1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3 
4 /*************************************************************************
5  * Copyright (c) 2011 AT&T Intellectual Property
6  * All rights reserved. This program and the accompanying materials
7  * are made available under the terms of the Eclipse Public License v1.0
8  * which accompanies this distribution, and is available at
9  * http://www.eclipse.org/legal/epl-v10.html
10  *
11  * Contributors: See CVS logs. Details at http://www.graphviz.org/
12  *************************************************************************/
13 
14 
15 /*
16  * position(g): set ND_coord(n) (x and y) for all nodes n of g, using GD_rank(g).
17  * (the graph may be modified by merging certain edges with a common endpoint.)
18  * the coordinates are computed by constructing and ranking an auxiliary graph.
19  * then leaf nodes are inserted in the fast graph.  cluster boundary nodes are
20  * created and correctly separated.
21  */
22 
23 #include "dot.h"
24 #include "aspect.h"
25 
26 static int nsiter2(graph_t * g);
27 static void create_aux_edges(graph_t * g);
28 static void remove_aux_edges(graph_t * g);
29 static void set_xcoords(graph_t * g);
30 static void set_ycoords(graph_t * g);
31 static void set_aspect(graph_t * g, aspect_t* );
32 static void expand_leaves(graph_t * g);
33 static void make_lrvn(graph_t * g);
34 static void contain_nodes(graph_t * g);
35 static boolean idealsize(graph_t * g, double);
36 
37 #if DEBUG > 1
38 static void
dumpNS(graph_t * g)39 dumpNS (graph_t * g)
40 {
41     node_t* n = GD_nlist(g);
42     elist el;
43     edge_t* e;
44     int i;
45 
46     while (n) {
47 	el = ND_out(n);
48 	for (i = 0; i < el.size; i++) {
49 	    e = el.list[i];
50 	    fprintf (stderr, "%s(%x) -> ", agnameof(agtail(e)),agtail(e));
51 	    fprintf (stderr, "%s(%x) : %d\n", agnameof(aghead(e)), aghead(e),
52 		ED_minlen(e));
53 	}
54 	n = ND_next(n);
55     }
56 }
57 #endif
58 
59 static double
largeMinlen(double l)60 largeMinlen (double l)
61 {
62     agerr (AGERR, "Edge length %f larger than maximum %u allowed.\nCheck for overwide node(s).\n", l, USHRT_MAX);
63     return (double)USHRT_MAX;
64 }
65 
66 /* connectGraph:
67  * When source and/or sink nodes are defined, it is possible that
68  * after the auxiliary edges are added, the graph may still have 2 or
69  * 3 components. To fix this, we put trivial constraints connecting the
70  * first items of each rank.
71  */
72 static void
connectGraph(graph_t * g)73 connectGraph (graph_t* g)
74 {
75     int i, j, r, found;
76     node_t* tp;
77     node_t* hp;
78     node_t* sn;
79     edge_t* e;
80     rank_t* rp;
81 
82     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
83 	rp = GD_rank(g)+r;
84 	found =FALSE;
85         tp = NULL;
86 	for (i = 0; i < rp->n; i++) {
87 	    tp = rp->v[i];
88 	    if (ND_save_out(tp).list) {
89         	for (j = 0; (e = ND_save_out(tp).list[j]); j++) {
90 		    if ((ND_rank(aghead(e)) > r) || (ND_rank(agtail(e)) > r)) {
91 			found = TRUE;
92 			break;
93 		    }
94         	}
95 		if (found) break;
96 	    }
97 	    if (ND_save_in(tp).list) {
98         	for (j = 0; (e = ND_save_in(tp).list[j]); j++) {
99 		    if ((ND_rank(agtail(e)) > r) || (ND_rank(aghead(e)) > r)) {
100 			found = TRUE;
101 			break;
102 		    }
103         	}
104 		if (found) break;
105 	    }
106 	}
107 	if (found || !tp) continue;
108 	tp = rp->v[0];
109 	if (r < GD_maxrank(g)) hp = (rp+1)->v[0];
110 	else hp = (rp-1)->v[0];
111 	assert (hp);
112 	sn = virtual_node(g);
113 	ND_node_type(sn) = SLACKNODE;
114 	make_aux_edge(sn, tp, 0, 0);
115 	make_aux_edge(sn, hp, 0, 0);
116 	ND_rank(sn) = MIN(ND_rank(tp), ND_rank(hp));
117     }
118 }
119 
dot_position(graph_t * g,aspect_t * asp)120 void dot_position(graph_t * g, aspect_t* asp)
121 {
122     if (GD_nlist(g) == NULL)
123 	return;			/* ignore empty graph */
124     mark_lowclusters(g);	/* we could remove from splines.c now */
125     set_ycoords(g);
126     if (Concentrate)
127 	dot_concentrate(g);
128     expand_leaves(g);
129     if (flat_edges(g))
130 	set_ycoords(g);
131     create_aux_edges(g);
132     if (rank(g, 2, nsiter2(g))) { /* LR balance == 2 */
133 	connectGraph (g);
134 	const int rank_result = rank(g, 2, nsiter2(g));
135 	assert(rank_result == 0);
136     }
137     set_xcoords(g);
138     set_aspect(g, asp);
139     remove_aux_edges(g);	/* must come after set_aspect since we now
140 				 * use GD_ln and GD_rn for bbox width.
141 				 */
142 }
143 
nsiter2(graph_t * g)144 static int nsiter2(graph_t * g)
145 {
146     int maxiter = INT_MAX;
147     char *s;
148 
149     if ((s = agget(g, "nslimit")))
150 	maxiter = atof(s) * agnnodes(g);
151     return maxiter;
152 }
153 
go(node_t * u,node_t * v)154 static int go(node_t * u, node_t * v)
155 {
156     int i;
157     edge_t *e;
158 
159     if (u == v)
160 	return TRUE;
161     for (i = 0; (e = ND_out(u).list[i]); i++) {
162 	if (go(aghead(e), v))
163 	    return TRUE;
164     }
165     return FALSE;
166 }
167 
canreach(node_t * u,node_t * v)168 static int canreach(node_t * u, node_t * v)
169 {
170     return go(u, v);
171 }
172 
make_aux_edge(node_t * u,node_t * v,double len,int wt)173 edge_t *make_aux_edge(node_t * u, node_t * v, double len, int wt)
174 {
175     edge_t *e;
176 
177     Agedgepair_t* e2 = NEW(Agedgepair_t);
178     AGTYPE(&(e2->in)) = AGINEDGE;
179     AGTYPE(&(e2->out)) = AGOUTEDGE;
180     e2->out.base.data = (Agrec_t*)NEW(Agedgeinfo_t);
181     e = &(e2->out);
182 
183     agtail(e) = u;
184     aghead(e) = v;
185     if (len > USHRT_MAX)
186 	len = largeMinlen (len);
187     ED_minlen(e) = ROUND(len);
188     ED_weight(e) = wt;
189     fast_edge(e);
190     return e;
191 }
192 
allocate_aux_edges(graph_t * g)193 static void allocate_aux_edges(graph_t * g)
194 {
195     int i, j, n_in;
196     node_t *n;
197 
198     /* allocate space for aux edge lists */
199     for (n = GD_nlist(g); n; n = ND_next(n)) {
200 	ND_save_in(n) = ND_in(n);
201 	ND_save_out(n) = ND_out(n);
202 	for (i = 0; ND_out(n).list[i]; i++);
203 	for (j = 0; ND_in(n).list[j]; j++);
204 	n_in = i + j;
205 	alloc_elist(n_in + 3, ND_in(n));
206 	alloc_elist(3, ND_out(n));
207     }
208 }
209 
210 /* make_LR_constraints:
211  */
212 static void
make_LR_constraints(graph_t * g)213 make_LR_constraints(graph_t * g)
214 {
215     int i, j, k;
216     int sw;			/* self width */
217     int m0, m1;
218     double width;
219     int sep[2];
220     int nodesep;      /* separation between nodes on same rank */
221     edge_t *e, *e0, *e1, *ff;
222     node_t *u, *v, *t0, *h0;
223     rank_t *rank = GD_rank(g);
224 
225     /* Use smaller separation on odd ranks if g has edge labels */
226     if (GD_has_labels(g->root) & EDGE_LABEL) {
227 	sep[0] = GD_nodesep(g);
228 	sep[1] = 5;
229     }
230     else {
231 	sep[1] = sep[0] = GD_nodesep(g);
232     }
233     /* make edges to constrain left-to-right ordering */
234     for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
235 	double last;
236 	last = ND_rank(rank[i].v[0]) = 0;
237 	nodesep = sep[i & 1];
238 	for (j = 0; j < rank[i].n; j++) {
239 	    u = rank[i].v[j];
240 	    ND_mval(u) = ND_rw(u);	/* keep it somewhere safe */
241 	    if (ND_other(u).size > 0) {	/* compute self size */
242 		/* FIX: dot assumes all self-edges go to the right. This
243                  * is no longer true, though makeSelfEdge still attempts to
244                  * put as many as reasonable on the right. The dot code
245                  * should be modified to allow a box reflecting the placement
246                  * of all self-edges, and use that to reposition the nodes.
247                  * Note that this would not only affect left and right
248                  * positioning but may also affect interrank spacing.
249                  */
250 		sw = 0;
251 		for (k = 0; (e = ND_other(u).list[k]); k++) {
252 		    if (agtail(e) == aghead(e)) {
253 			sw += selfRightSpace (e);
254 		    }
255 		}
256 		ND_rw(u) += sw;	/* increment to include self edges */
257 	    }
258 	    v = rank[i].v[j + 1];
259 	    if (v) {
260 		width = ND_rw(u) + ND_lw(v) + nodesep;
261 		e0 = make_aux_edge(u, v, width, 0);
262 		last = (ND_rank(v) = last + width);
263 	    }
264 
265 	    /* constraints from labels of flat edges on previous rank */
266 	    if ((e = (edge_t*)ND_alg(u))) {
267 		e0 = ND_save_out(u).list[0];
268 		e1 = ND_save_out(u).list[1];
269 		if (ND_order(aghead(e0)) > ND_order(aghead(e1))) {
270 		    ff = e0;
271 		    e0 = e1;
272 		    e1 = ff;
273 		}
274 		m0 = (ED_minlen(e) * GD_nodesep(g)) / 2;
275 		m1 = m0 + ND_rw(aghead(e0)) + ND_lw(agtail(e0));
276 		/* these guards are needed because the flat edges
277 		 * work very poorly with cluster layout */
278 		if (canreach(agtail(e0), aghead(e0)) == FALSE)
279 		    make_aux_edge(aghead(e0), agtail(e0), m1,
280 			ED_weight(e));
281 		m1 = m0 + ND_rw(agtail(e1)) + ND_lw(aghead(e1));
282 		if (canreach(aghead(e1), agtail(e1)) == FALSE)
283 		    make_aux_edge(agtail(e1), aghead(e1), m1,
284 			ED_weight(e));
285 	    }
286 
287 	    /* position flat edge endpoints */
288 	    for (k = 0; k < ND_flat_out(u).size; k++) {
289 		e = ND_flat_out(u).list[k];
290 		if (ND_order(agtail(e)) < ND_order(aghead(e))) {
291 		    t0 = agtail(e);
292 		    h0 = aghead(e);
293 		} else {
294 		    t0 = aghead(e);
295 		    h0 = agtail(e);
296 		}
297 
298 		width = ND_rw(t0) + ND_lw(h0);
299 		m0 = ED_minlen(e) * GD_nodesep(g) + width;
300 
301 		if ((e0 = find_fast_edge(t0, h0))) {
302 		    /* flat edge between adjacent neighbors
303                      * ED_dist contains the largest label width.
304                      */
305 		    m0 = MAX(m0, width + GD_nodesep(g) + ROUND(ED_dist(e)));
306 		    if (m0 > USHRT_MAX)
307 			m0 = largeMinlen (m0);
308 		    ED_minlen(e0) = MAX(ED_minlen(e0), m0);
309 		    ED_weight(e0) = MAX(ED_weight(e0), ED_weight(e));
310 		}
311 		else if (!ED_label(e)) {
312 		    /* unlabeled flat edge between non-neighbors
313 		     * ED_minlen(e) is max of ED_minlen of all equivalent
314                      * edges.
315                      */
316 		    make_aux_edge(t0, h0, m0, ED_weight(e));
317 		}
318 		/* labeled flat edges between non-neighbors have already
319                  * been constrained by the label above.
320                  */
321 	    }
322 	}
323     }
324 }
325 
326 /* make_edge_pairs: make virtual edge pairs corresponding to input edges */
make_edge_pairs(graph_t * g)327 static void make_edge_pairs(graph_t * g)
328 {
329     int i, m0, m1;
330     node_t *n, *sn;
331     edge_t *e;
332 
333     for (n = GD_nlist(g); n; n = ND_next(n)) {
334 	if (ND_save_out(n).list)
335 	    for (i = 0; (e = ND_save_out(n).list[i]); i++) {
336 		sn = virtual_node(g);
337 		ND_node_type(sn) = SLACKNODE;
338 		m0 = (ED_head_port(e).p.x - ED_tail_port(e).p.x);
339 		if (m0 > 0)
340 		    m1 = 0;
341 		else {
342 		    m1 = -m0;
343 		    m0 = 0;
344 		}
345 #ifdef NOTDEF
346 /* was trying to improve LR balance */
347 		if ((ND_save_out(n).size % 2 == 0)
348 		    && (i == ND_save_out(n).size / 2 - 1)) {
349 		    node_t *u = ND_save_out(n).list[i]->head;
350 		    node_t *v = ND_save_out(n).list[i + 1]->head;
351 		    double width = ND_rw(u) + ND_lw(v) + GD_nodesep(g);
352 		    m0 = width / 2 - 1;
353 		}
354 #endif
355 		make_aux_edge(sn, agtail(e), m0 + 1, ED_weight(e));
356 		make_aux_edge(sn, aghead(e), m1 + 1, ED_weight(e));
357 		ND_rank(sn) =
358 		    MIN(ND_rank(agtail(e)) - m0 - 1,
359 			ND_rank(aghead(e)) - m1 - 1);
360 	    }
361     }
362 }
363 
contain_clustnodes(graph_t * g)364 static void contain_clustnodes(graph_t * g)
365 {
366     int c;
367     edge_t	*e;
368 
369     if (g != dot_root(g)) {
370 	contain_nodes(g);
371 	if ((e = find_fast_edge(GD_ln(g),GD_rn(g))))	/* maybe from lrvn()?*/
372 	    ED_weight(e) += 128;
373 	else
374 	    make_aux_edge(GD_ln(g), GD_rn(g), 1, 128);	/* clust compaction edge */
375     }
376     for (c = 1; c <= GD_n_cluster(g); c++)
377 	contain_clustnodes(GD_clust(g)[c]);
378 }
379 
vnode_not_related_to(graph_t * g,node_t * v)380 static int vnode_not_related_to(graph_t * g, node_t * v)
381 {
382     edge_t *e;
383 
384     if (ND_node_type(v) != VIRTUAL)
385 	return FALSE;
386     for (e = ND_save_out(v).list[0]; ED_to_orig(e); e = ED_to_orig(e));
387     if (agcontains(g, agtail(e)))
388 	return FALSE;
389     if (agcontains(g, aghead(e)))
390 	return FALSE;
391     return TRUE;
392 }
393 
394 /* keepout_othernodes:
395  * Guarantee nodes outside the cluster g are placed outside of it.
396  * This is done by adding constraints to make sure such nodes have
397  * a gap of margin from the left or right bounding box node ln or rn.
398  *
399  * We could probably reduce some of these constraints by checking if
400  * the node is in a cluster, since elsewhere we make constrain a
401  * separate between clusters. Also, we should be able to skip the
402  * first loop if g is the root graph.
403  */
keepout_othernodes(graph_t * g)404 static void keepout_othernodes(graph_t * g)
405 {
406     int i, c, r, margin;
407     node_t *u, *v;
408 
409     margin = late_int (g, G_margin, CL_OFFSET, 0);
410     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
411 	if (GD_rank(g)[r].n == 0)
412 	    continue;
413 	v = GD_rank(g)[r].v[0];
414 	if (v == NULL)
415 	    continue;
416 	for (i = ND_order(v) - 1; i >= 0; i--) {
417 	    u = GD_rank(dot_root(g))[r].v[i];
418 	    /* can't use "is_a_vnode_of" because elists are swapped */
419 	    if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g, u)) {
420 		make_aux_edge(u, GD_ln(g), margin + ND_rw(u), 0);
421 		break;
422 	    }
423 	}
424 	for (i = ND_order(v) + GD_rank(g)[r].n; i < GD_rank(dot_root(g))[r].n;
425 	     i++) {
426 	    u = GD_rank(dot_root(g))[r].v[i];
427 	    if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g, u)) {
428 		make_aux_edge(GD_rn(g), u, margin + ND_lw(u), 0);
429 		break;
430 	    }
431 	}
432     }
433 
434     for (c = 1; c <= GD_n_cluster(g); c++)
435 	keepout_othernodes(GD_clust(g)[c]);
436 }
437 
438 /* contain_subclust:
439  * Make sure boxes of subclusters of g are offset from the
440  * box of g. This is done by a constraint between the left and
441  * right bounding box nodes ln and rn of g and a subcluster.
442  * The gap needs to include any left or right labels.
443  */
contain_subclust(graph_t * g)444 static void contain_subclust(graph_t * g)
445 {
446     int margin, c;
447     graph_t *subg;
448 
449     margin = late_int (g, G_margin, CL_OFFSET, 0);
450     make_lrvn(g);
451     for (c = 1; c <= GD_n_cluster(g); c++) {
452 	subg = GD_clust(g)[c];
453 	make_lrvn(subg);
454 	make_aux_edge(GD_ln(g), GD_ln(subg),
455 		      margin + GD_border(g)[LEFT_IX].x, 0);
456 	make_aux_edge(GD_rn(subg), GD_rn(g),
457 		      margin + GD_border(g)[RIGHT_IX].x, 0);
458 	contain_subclust(subg);
459     }
460 }
461 
462 /* separate_subclust:
463  * Guarantee space between subcluster of g.
464  * This is done by adding a constraint between the right bbox node rn
465  * of the left cluster and the left bbox node ln of the right cluster.
466  * This is only done if the two clusters overlap in some rank.
467  */
separate_subclust(graph_t * g)468 static void separate_subclust(graph_t * g)
469 {
470     int i, j, margin;
471     graph_t *low, *high;
472     graph_t *left, *right;
473 
474     margin = late_int (g, G_margin, CL_OFFSET, 0);
475     for (i = 1; i <= GD_n_cluster(g); i++)
476 	make_lrvn(GD_clust(g)[i]);
477     for (i = 1; i <= GD_n_cluster(g); i++) {
478 	for (j = i + 1; j <= GD_n_cluster(g); j++) {
479 	    low = GD_clust(g)[i];
480 	    high = GD_clust(g)[j];
481 	    if (GD_minrank(low) > GD_minrank(high)) {
482 		graph_t *temp = low;
483 		low = high;
484 		high = temp;
485 	    }
486 	    if (GD_maxrank(low) < GD_minrank(high))
487 		continue;
488 	    if (ND_order(GD_rank(low)[GD_minrank(high)].v[0])
489 		< ND_order(GD_rank(high)[GD_minrank(high)].v[0])) {
490 		left = low;
491 		right = high;
492 	    } else {
493 		left = high;
494 		right = low;
495 	    }
496 	    make_aux_edge(GD_rn(left), GD_ln(right), margin, 0);
497 	}
498 	separate_subclust(GD_clust(g)[i]);
499     }
500 }
501 
502 /* pos_clusters: create constraints for:
503  *	node containment in clusters,
504  *	cluster containment in clusters,
505  *	separation of sibling clusters.
506  */
pos_clusters(graph_t * g)507 static void pos_clusters(graph_t * g)
508 {
509     if (GD_n_cluster(g) > 0) {
510 	contain_clustnodes(g);
511 	keepout_othernodes(g);
512 	contain_subclust(g);
513 	separate_subclust(g);
514     }
515 }
516 
compress_graph(graph_t * g)517 static void compress_graph(graph_t * g)
518 {
519     double x;
520     pointf p;
521 
522     if (GD_drawing(g)->ratio_kind != R_COMPRESS)
523 	return;
524     p = GD_drawing(g)->size;
525     if (p.x * p.y <= 1)
526 	return;
527     contain_nodes(g);
528     if (GD_flip(g) == FALSE)
529 	x = p.x;
530     else
531 	x = p.y;
532 
533     /* Guard against huge size attribute since max. edge length is USHRT_MAX
534      * A warning might be called for. Also, one could check that the graph
535      * already fits GD_drawing(g)->size and return immediately.
536      */
537     x = MIN(x,USHRT_MAX);
538     make_aux_edge(GD_ln(g), GD_rn(g), x, 1000);
539 }
540 
create_aux_edges(graph_t * g)541 static void create_aux_edges(graph_t * g)
542 {
543     allocate_aux_edges(g);
544     make_LR_constraints(g);
545     make_edge_pairs(g);
546     pos_clusters(g);
547     compress_graph(g);
548 }
549 
remove_aux_edges(graph_t * g)550 static void remove_aux_edges(graph_t * g)
551 {
552     int i;
553     node_t *n, *nnext, *nprev;
554     edge_t *e;
555 
556     for (n = GD_nlist(g); n; n = ND_next(n)) {
557 	for (i = 0; (e = ND_out(n).list[i]); i++) {
558 	    free(e->base.data);
559 	    free(e);
560 	}
561 	free_list(ND_out(n));
562 	free_list(ND_in(n));
563 	ND_out(n) = ND_save_out(n);
564 	ND_in(n) = ND_save_in(n);
565     }
566     /* cannot be merged with previous loop */
567     nprev = NULL;
568     for (n = GD_nlist(g); n; n = nnext) {
569 	nnext = ND_next(n);
570 	if (ND_node_type(n) == SLACKNODE) {
571 	    if (nprev)
572 		ND_next(nprev) = nnext;
573 	    else
574 		GD_nlist(g) = nnext;
575 	    free(n->base.data);
576 	    free(n);
577 	} else
578 	    nprev = n;
579     }
580     ND_prev(GD_nlist(g)) = NULL;
581 }
582 
583 /* set_xcoords:
584  * Set x coords of nodes.
585  */
586 static void
set_xcoords(graph_t * g)587 set_xcoords(graph_t * g)
588 {
589     int i, j;
590     node_t *v;
591     rank_t *rank = GD_rank(g);
592 
593     for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
594 	for (j = 0; j < rank[i].n; j++) {
595 	    v = rank[i].v[j];
596 	    ND_coord(v).x = ND_rank(v);
597 	    ND_rank(v) = i;
598 	}
599     }
600 }
601 
602 /* adjustSimple:
603  * Expand cluster height by delta, adding half to top
604  * and half to bottom. If the bottom expansion exceeds the
605  * ht1 of the rank, shift the ranks in the cluster up.
606  * If the top expansion, including any shift from the bottom
607  * expansion, exceeds to ht2 of the rank, shift the ranks above
608  * the cluster up.
609  *
610  * FIX: There can be excess space between ranks. Not sure where this is
611  * coming from but it could be cleaned up.
612  */
adjustSimple(graph_t * g,int delta,int margin_total)613 static void adjustSimple(graph_t * g, int delta, int margin_total)
614 {
615     int r, bottom, deltop, delbottom;
616     graph_t *root = dot_root(g);
617     rank_t *rank = GD_rank(root);
618     int maxr = GD_maxrank(g);
619     int minr = GD_minrank(g);
620 
621     bottom = (delta+1) / 2;
622     delbottom = GD_ht1(g) + bottom - (rank[maxr].ht1 - margin_total);
623     if (delbottom > 0) {
624 	for (r = maxr; r >= minr; r--) {
625 	    if (rank[r].n > 0)
626 		ND_coord(rank[r].v[0]).y += delbottom;
627  	}
628 	deltop = GD_ht2(g) + (delta-bottom) + delbottom - (rank[minr].ht2 - margin_total);
629     }
630     else
631 	deltop = GD_ht2(g) + (delta-bottom) - (rank[minr].ht2 - margin_total);
632     if (deltop > 0) {
633 	for (r = minr-1; r >= GD_minrank(root); r--) {
634 	    if (rank[r].n > 0)
635 		ND_coord(rank[r].v[0]).y += deltop;
636 	}
637     }
638     GD_ht2(g) += (delta - bottom);
639     GD_ht1(g) += bottom;
640 }
641 
642 /* adjustRanks:
643  * Recursively adjust ranks to take into account
644  * wide cluster labels when rankdir=LR.
645  * We divide the extra space between the top and bottom.
646  * Adjust the ht1 and ht2 values in the process.
647  */
adjustRanks(graph_t * g,int margin_total)648 static void adjustRanks(graph_t * g, int margin_total)
649 {
650     double lht;			/* label height */
651     double rht;			/* height between top and bottom ranks */
652     int maxr, minr, margin;
653     int c;
654     double delta, ht1, ht2;
655 
656     rank_t *rank = GD_rank(dot_root(g));
657     if (g == dot_root(g))
658 	margin = 0;
659     else
660 	margin = late_int (g, G_margin, CL_OFFSET, 0);
661 
662     ht1 = GD_ht1(g);
663     ht2 = GD_ht2(g);
664 
665     for (c = 1; c <= GD_n_cluster(g); c++) {
666 	graph_t *subg = GD_clust(g)[c];
667 	adjustRanks(subg, margin+margin_total);
668 	if (GD_maxrank(subg) == GD_maxrank(g))
669 	    ht1 = MAX(ht1, GD_ht1(subg) + margin);
670 	if (GD_minrank(subg) == GD_minrank(g))
671 	    ht2 = MAX(ht2, GD_ht2(subg) + margin);
672     }
673 
674     GD_ht1(g) = ht1;
675     GD_ht2(g) = ht2;
676 
677     if ((g != dot_root(g)) && GD_label(g)) {
678 	lht = MAX(GD_border(g)[LEFT_IX].y, GD_border(g)[RIGHT_IX].y);
679 	maxr = GD_maxrank(g);
680 	minr = GD_minrank(g);
681 	rht = ND_coord(rank[minr].v[0]).y - ND_coord(rank[maxr].v[0]).y;
682 	delta = lht - (rht + ht1 + ht2);
683 	if (delta > 0) {
684 	    adjustSimple(g, delta, margin_total);
685 	}
686     }
687 
688     /* update the global ranks */
689     if (g != dot_root(g)) {
690 	rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, GD_ht2(g));
691 	rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, GD_ht1(g));
692     }
693 }
694 
695 /* clust_ht:
696  * recursively compute cluster ht requirements.  assumes GD_ht1(subg) and ht2
697  * are computed from primitive nodes only.  updates ht1 and ht2 to reflect
698  * cluster nesting and labels.  also maintains global rank ht1 and ht2.
699  * Return true if some cluster has a label.
700  */
clust_ht(Agraph_t * g)701 static int clust_ht(Agraph_t * g)
702 {
703     int c;
704     double ht1, ht2;
705     graph_t *subg;
706     rank_t *rank = GD_rank(dot_root(g));
707     int margin, haveClustLabel = 0;
708 
709     if (g == dot_root(g))
710 	margin = CL_OFFSET;
711     else
712 	margin = late_int (g, G_margin, CL_OFFSET, 0);
713 
714     ht1 = GD_ht1(g);
715     ht2 = GD_ht2(g);
716 
717     /* account for sub-clusters */
718     for (c = 1; c <= GD_n_cluster(g); c++) {
719 	subg = GD_clust(g)[c];
720 	haveClustLabel |= clust_ht(subg);
721 	if (GD_maxrank(subg) == GD_maxrank(g))
722 	    ht1 = MAX(ht1, GD_ht1(subg) + margin);
723 	if (GD_minrank(subg) == GD_minrank(g))
724 	    ht2 = MAX(ht2, GD_ht2(subg) + margin);
725     }
726 
727     /* account for a possible cluster label in clusters */
728     /* room for root graph label is handled in dotneato_postprocess */
729     if ((g != dot_root(g)) && GD_label(g)) {
730 	haveClustLabel = 1;
731 	if (!GD_flip(agroot(g))) {
732 	    ht1 += GD_border(g)[BOTTOM_IX].y;
733 	    ht2 += GD_border(g)[TOP_IX].y;
734 	}
735     }
736     GD_ht1(g) = ht1;
737     GD_ht2(g) = ht2;
738 
739     /* update the global ranks */
740     if (g != dot_root(g)) {
741 	rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, ht2);
742 	rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, ht1);
743     }
744 
745     return haveClustLabel;
746 }
747 
748 /* set y coordinates of nodes, a rank at a time */
set_ycoords(graph_t * g)749 static void set_ycoords(graph_t * g)
750 {
751     int i, j, r;
752     double ht2, maxht, delta, d0, d1;
753     node_t *n;
754     edge_t *e;
755     rank_t *rank = GD_rank(g);
756     graph_t *clust;
757     int lbl;
758 
759     ht2 = maxht = 0;
760 
761     /* scan ranks for tallest nodes.  */
762     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
763 	for (i = 0; i < rank[r].n; i++) {
764 	    n = rank[r].v[i];
765 
766 	    /* assumes symmetry, ht1 = ht2 */
767 	    ht2 = ND_ht(n) / 2;
768 
769 
770 	    /* have to look for high self-edge labels, too */
771 	    if (ND_other(n).list)
772 		for (j = 0; (e = ND_other(n).list[j]); j++) {
773 		    if (agtail(e) == aghead(e)) {
774 			if (ED_label(e))
775 			    ht2 = MAX(ht2, ED_label(e)->dimen.y / 2);
776 		    }
777 		}
778 
779 	    /* update global rank ht */
780 	    if (rank[r].pht2 < ht2)
781 		rank[r].pht2 = rank[r].ht2 = ht2;
782 	    if (rank[r].pht1 < ht2)
783 		rank[r].pht1 = rank[r].ht1 = ht2;
784 
785 	    /* update nearest enclosing cluster rank ht */
786 	    if ((clust = ND_clust(n))) {
787 		int yoff = (clust == g ? 0 : late_int (clust, G_margin, CL_OFFSET, 0));
788 		if (ND_rank(n) == GD_minrank(clust))
789 		    GD_ht2(clust) = MAX(GD_ht2(clust), ht2 + yoff);
790 		if (ND_rank(n) == GD_maxrank(clust))
791 		    GD_ht1(clust) = MAX(GD_ht1(clust), ht2 + yoff);
792 	    }
793 	}
794     }
795 
796     /* scan sub-clusters */
797     lbl = clust_ht(g);
798 
799     /* make the initial assignment of ycoords to leftmost nodes by ranks */
800     maxht = 0;
801     r = GD_maxrank(g);
802     (ND_coord(rank[r].v[0])).y = rank[r].ht1;
803     while (--r >= GD_minrank(g)) {
804 	d0 = rank[r + 1].pht2 + rank[r].pht1 + GD_ranksep(g);	/* prim node sep */
805 	d1 = rank[r + 1].ht2 + rank[r].ht1 + CL_OFFSET;	/* cluster sep */
806 	delta = MAX(d0, d1);
807 	if (rank[r].n > 0)	/* this may reflect some problem */
808 		(ND_coord(rank[r].v[0])).y = (ND_coord(rank[r + 1].v[0])).y + delta;
809 #ifdef DEBUG
810 	else
811 	    fprintf(stderr, "dot set_ycoords: rank %d is empty\n",
812 		    rank[r].n);
813 #endif
814 	maxht = MAX(maxht, delta);
815     }
816 
817     /* If there are cluster labels and the drawing is rotated, we need special processing to
818      * allocate enough room. We use adjustRanks for this, and then recompute the maxht if
819      * the ranks are to be equally spaced. This seems simpler and appears to work better than
820      * handling equal spacing as a special case.
821      */
822     if (lbl && GD_flip(g)) {
823 	adjustRanks(g, 0);
824 	if (GD_exact_ranksep(g)) {  /* recompute maxht */
825 	    maxht = 0;
826 	    r = GD_maxrank(g);
827 	    d0 = (ND_coord(rank[r].v[0])).y;
828 	    while (--r >= GD_minrank(g)) {
829 		d1 = (ND_coord(rank[r].v[0])).y;
830 		delta = d1 - d0;
831 		maxht = MAX(maxht, delta);
832 		d0 = d1;
833 	    }
834 	}
835     }
836 
837     /* re-assign if ranks are equally spaced */
838     if (GD_exact_ranksep(g)) {
839 	for (r = GD_maxrank(g) - 1; r >= GD_minrank(g); r--)
840 	    if (rank[r].n > 0)	/* this may reflect the same problem :-() */
841 			(ND_coord(rank[r].v[0])).y=
842 		    (ND_coord(rank[r + 1].v[0])).y + maxht;
843     }
844 
845     /* copy ycoord assignment from leftmost nodes to others */
846     for (n = GD_nlist(g); n; n = ND_next(n))
847 	ND_coord(n).y = (ND_coord(rank[ND_rank(n)].v[0])).y;
848 }
849 
850 /* dot_compute_bb:
851  * Compute bounding box of g.
852  * The x limits of clusters are given by the x positions of ln and rn.
853  * This information is stored in the rank field, since it was calculated
854  * using network simplex.
855  * For the root graph, we don't enforce all the constraints on lr and
856  * rn, so we traverse the nodes and subclusters.
857  */
dot_compute_bb(graph_t * g,graph_t * root)858 static void dot_compute_bb(graph_t * g, graph_t * root)
859 {
860     int r, c;
861     double x, offset;
862     node_t *v;
863     pointf LL, UR;
864 
865     if (g == dot_root(g)) {
866 	LL.x = (double)(INT_MAX);
867 	UR.x = (double)(-INT_MAX);
868 	for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
869 	    int rnkn = GD_rank(g)[r].n;
870 	    if (rnkn == 0)
871 		continue;
872 	    if ((v = GD_rank(g)[r].v[0]) == NULL)
873 		continue;
874 	    for (c = 1; (ND_node_type(v) != NORMAL) && c < rnkn; c++)
875 		v = GD_rank(g)[r].v[c];
876 	    if (ND_node_type(v) == NORMAL) {
877 		x = ND_coord(v).x - ND_lw(v);
878 		LL.x = MIN(LL.x, x);
879 	    }
880 	    else continue;
881 		/* At this point, we know the rank contains a NORMAL node */
882 	    v = GD_rank(g)[r].v[rnkn - 1];
883 	    for (c = rnkn-2; ND_node_type(v) != NORMAL; c--)
884 		v = GD_rank(g)[r].v[c];
885 	    x = ND_coord(v).x + ND_rw(v);
886 	    UR.x = MAX(UR.x, x);
887 	}
888 	offset = CL_OFFSET;
889 	for (c = 1; c <= GD_n_cluster(g); c++) {
890 	    x = (double)(GD_bb(GD_clust(g)[c]).LL.x - offset);
891 	    LL.x = MIN(LL.x, x);
892 	    x = (double)(GD_bb(GD_clust(g)[c]).UR.x + offset);
893 	    UR.x = MAX(UR.x, x);
894 	}
895     } else {
896 	LL.x = (double)(ND_rank(GD_ln(g)));
897 	UR.x = (double)(ND_rank(GD_rn(g)));
898     }
899     LL.y = ND_coord(GD_rank(root)[GD_maxrank(g)].v[0]).y - GD_ht1(g);
900     UR.y = ND_coord(GD_rank(root)[GD_minrank(g)].v[0]).y + GD_ht2(g);
901     GD_bb(g).LL = LL;
902     GD_bb(g).UR = UR;
903 }
904 
rec_bb(graph_t * g,graph_t * root)905 static void rec_bb(graph_t * g, graph_t * root)
906 {
907     int c;
908     for (c = 1; c <= GD_n_cluster(g); c++)
909 	rec_bb(GD_clust(g)[c], root);
910     dot_compute_bb(g, root);
911 }
912 
913 /* scale_bb:
914  * Recursively rescale all bounding boxes using scale factors
915  * xf and yf. We assume all the bboxes have been computed.
916  */
scale_bb(graph_t * g,graph_t * root,double xf,double yf)917 static void scale_bb(graph_t * g, graph_t * root, double xf, double yf)
918 {
919     int c;
920 
921     for (c = 1; c <= GD_n_cluster(g); c++)
922 	scale_bb(GD_clust(g)[c], root, xf, yf);
923     GD_bb(g).LL.x *= xf;
924     GD_bb(g).LL.y *= yf;
925     GD_bb(g).UR.x *= xf;
926     GD_bb(g).UR.y *= yf;
927 }
928 
929 /* adjustAspectRatio:
930  */
adjustAspectRatio(graph_t * g,aspect_t * asp)931 static void adjustAspectRatio (graph_t* g, aspect_t* asp)
932 {
933     double AR = (GD_bb(g).UR.x - GD_bb(g).LL.x)/(GD_bb(g).UR.y - GD_bb(g).LL.y);
934     if (Verbose) {
935         fprintf(stderr, "AR=%0.4lf\t Area= %0.4lf\t", AR, (double)(GD_bb(g).UR.x - GD_bb(g).LL.x)*(GD_bb(g).UR.y - GD_bb(g).LL.y)/10000.0);
936         fprintf(stderr, "Dummy=%d\n", countDummyNodes(g));
937     }
938     if (AR > 1.1*asp->targetAR) {
939       asp->nextIter = (int)(asp->targetAR * (double)(asp->curIterations - asp->prevIterations)/(AR));
940     }
941     else if (AR <= 0.8 * asp->targetAR) {
942       asp->nextIter = -1;
943       if (Verbose)
944         fprintf(stderr, "Going to apply another expansion.\n");
945     }
946     else {
947 	asp->nextIter = 0;
948     }
949     if (Verbose)
950         fprintf(stderr, "next#iter=%d\n", asp->nextIter);
951 }
952 
953 /* set_aspect:
954  * Set bounding boxes and, if ratio is set, rescale graph.
955  * Note that if some dimension shrinks, there may be problems
956  * with labels.
957  */
set_aspect(graph_t * g,aspect_t * asp)958 static void set_aspect(graph_t * g, aspect_t* asp)
959 {
960     double xf = 0.0, yf = 0.0, actual, desired;
961     node_t *n;
962     boolean scale_it, filled;
963     point sz;
964 
965     rec_bb(g, g);
966     if ((GD_maxrank(g) > 0) && (GD_drawing(g)->ratio_kind)) {
967 	sz.x = GD_bb(g).UR.x - GD_bb(g).LL.x;
968 	sz.y = GD_bb(g).UR.y - GD_bb(g).LL.y;	/* normalize */
969 	if (GD_flip(g)) {
970 	    int t = sz.x;
971 	    sz.x = sz.y;
972 	    sz.y = t;
973 	}
974 	scale_it = TRUE;
975 	if (GD_drawing(g)->ratio_kind == R_AUTO)
976 	    filled = idealsize(g, .5);
977 	else
978 	    filled = (GD_drawing(g)->ratio_kind == R_FILL);
979 	if (filled) {
980 	    /* fill is weird because both X and Y can stretch */
981 	    if (GD_drawing(g)->size.x <= 0)
982 		scale_it = FALSE;
983 	    else {
984 		xf = (double) GD_drawing(g)->size.x / (double) sz.x;
985 		yf = (double) GD_drawing(g)->size.y / (double) sz.y;
986 		if ((xf < 1.0) || (yf < 1.0)) {
987 		    if (xf < yf) {
988 			yf = yf / xf;
989 			xf = 1.0;
990 		    } else {
991 			xf = xf / yf;
992 			yf = 1.0;
993 		    }
994 		}
995 	    }
996 	} else if (GD_drawing(g)->ratio_kind == R_EXPAND) {
997 	    if (GD_drawing(g)->size.x <= 0)
998 		scale_it = FALSE;
999 	    else {
1000 		xf = (double) GD_drawing(g)->size.x /
1001 		    (double) GD_bb(g).UR.x;
1002 		yf = (double) GD_drawing(g)->size.y /
1003 		    (double) GD_bb(g).UR.y;
1004 		if ((xf > 1.0) && (yf > 1.0)) {
1005 		    double scale = MIN(xf, yf);
1006 		    xf = yf = scale;
1007 		} else
1008 		    scale_it = FALSE;
1009 	    }
1010 	} else if (GD_drawing(g)->ratio_kind == R_VALUE) {
1011 	    desired = GD_drawing(g)->ratio;
1012 	    actual = ((double) sz.y) / ((double) sz.x);
1013 	    if (actual < desired) {
1014 		yf = desired / actual;
1015 		xf = 1.0;
1016 	    } else {
1017 		xf = actual / desired;
1018 		yf = 1.0;
1019 	    }
1020 	} else
1021 	    scale_it = FALSE;
1022 	if (scale_it) {
1023 	    if (GD_flip(g)) {
1024 		double t = xf;
1025 		xf = yf;
1026 		yf = t;
1027 	    }
1028 	    for (n = GD_nlist(g); n; n = ND_next(n)) {
1029 		ND_coord(n).x = ROUND(ND_coord(n).x * xf);
1030 		ND_coord(n).y = ROUND(ND_coord(n).y * yf);
1031 	    }
1032 	    scale_bb(g, g, xf, yf);
1033 	}
1034     }
1035 
1036     if (asp) adjustAspectRatio (g, asp);
1037 }
1038 
resize_leaf(node_t * leaf,point lbound)1039 static point resize_leaf(node_t * leaf, point lbound)
1040 {
1041     gv_nodesize(leaf, GD_flip(agraphof(leaf)));
1042     ND_coord(leaf).y = lbound.y;
1043     ND_coord(leaf).x = lbound.x + ND_lw(leaf);
1044     lbound.x = lbound.x + ND_lw(leaf) + ND_rw(leaf) + GD_nodesep(agraphof(leaf));
1045     return lbound;
1046 }
1047 
place_leaf(graph_t * ing,node_t * leaf,point lbound,int order)1048 static point place_leaf(graph_t* ing, node_t * leaf, point lbound, int order)
1049 {
1050     node_t *leader;
1051     graph_t *g = dot_root(ing);
1052 
1053     leader = UF_find(leaf);
1054     if (leaf != leader)
1055 	fast_nodeapp(leader, leaf);
1056     ND_order(leaf) = order;
1057     ND_rank(leaf) = ND_rank(leader);
1058     GD_rank(g)[ND_rank(leaf)].v[ND_order(leaf)] = leaf;
1059     return resize_leaf(leaf, lbound);
1060 }
1061 
1062 /* make space for the leaf nodes of each rank */
make_leafslots(graph_t * g)1063 static void make_leafslots(graph_t * g)
1064 {
1065     int i, j, r;
1066     node_t *v;
1067 
1068     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
1069 	j = 0;
1070 	for (i = 0; i < GD_rank(g)[r].n; i++) {
1071 	    v = GD_rank(g)[r].v[i];
1072 	    ND_order(v) = j;
1073 	    if (ND_ranktype(v) == LEAFSET)
1074 		j = j + ND_UF_size(v);
1075 	    else
1076 		j++;
1077 	}
1078 	if (j <= GD_rank(g)[r].n)
1079 	    continue;
1080 	GD_rank(g)[r].v = ALLOC(j + 1, GD_rank(g)[r].v, node_t *);
1081 	for (i = GD_rank(g)[r].n - 1; i >= 0; i--) {
1082 	    v = GD_rank(g)[r].v[i];
1083 	    GD_rank(g)[r].v[ND_order(v)] = v;
1084 	}
1085 	GD_rank(g)[r].n = j;
1086 	GD_rank(g)[r].v[j] = NULL;
1087     }
1088 }
1089 
do_leaves(graph_t * g,node_t * leader)1090 static void do_leaves(graph_t * g, node_t * leader)
1091 {
1092     int j;
1093     point lbound;
1094     node_t *n;
1095     edge_t *e;
1096 
1097     if (ND_UF_size(leader) <= 1)
1098 	return;
1099     lbound.x = ND_coord(leader).x - ND_lw(leader);
1100     lbound.y = ND_coord(leader).y;
1101     lbound = resize_leaf(leader, lbound);
1102     if (ND_out(leader).size > 0) {	/* in-edge leaves */
1103 	n = aghead(ND_out(leader).list[0]);
1104 	j = ND_order(leader) + 1;
1105 	for (e = agfstin(g, n); e; e = agnxtin(g, e)) {
1106 	    edge_t *e1 = AGMKOUT(e);
1107 	    if ((agtail(e1) != leader) && (UF_find(agtail(e1)) == leader)) {
1108 		lbound = place_leaf(g, agtail(e1), lbound, j++);
1109 		unmerge_oneway(e1);
1110 		elist_append(e1, ND_in(aghead(e1)));
1111 	    }
1112 	}
1113     } else {			/* out edge leaves */
1114 	n = agtail(ND_in(leader).list[0]);
1115 	j = ND_order(leader) + 1;
1116 	for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
1117 	    if ((aghead(e) != leader) && (UF_find(aghead(e)) == leader)) {
1118 		lbound = place_leaf(g, aghead(e), lbound, j++);
1119 		unmerge_oneway(e);
1120 		elist_append(e, ND_out(agtail(e)));
1121 	    }
1122 	}
1123     }
1124 }
1125 
ports_eq(edge_t * e,edge_t * f)1126 int ports_eq(edge_t * e, edge_t * f)
1127 {
1128     return ((ED_head_port(e).defined == ED_head_port(f).defined)
1129 	    && (((ED_head_port(e).p.x == ED_head_port(f).p.x) &&
1130 		 (ED_head_port(e).p.y == ED_head_port(f).p.y))
1131 		|| (ED_head_port(e).defined == FALSE))
1132 	    && (((ED_tail_port(e).p.x == ED_tail_port(f).p.x) &&
1133 		 (ED_tail_port(e).p.y == ED_tail_port(f).p.y))
1134 		|| (ED_tail_port(e).defined == FALSE))
1135 	);
1136 }
1137 
expand_leaves(graph_t * g)1138 static void expand_leaves(graph_t * g)
1139 {
1140     int i, d;
1141     node_t *n;
1142     edge_t *e, *f;
1143 
1144     make_leafslots(g);
1145     for (n = GD_nlist(g); n; n = ND_next(n)) {
1146 	if (ND_inleaf(n))
1147 	    do_leaves(g, ND_inleaf(n));
1148 	if (ND_outleaf(n))
1149 	    do_leaves(g, ND_outleaf(n));
1150 	if (ND_other(n).list)
1151 	    for (i = 0; (e = ND_other(n).list[i]); i++) {
1152 		if ((d = ND_rank(aghead(e)) - ND_rank(aghead(e))) == 0)
1153 		    continue;
1154 		f = ED_to_orig(e);
1155 		if (ports_eq(e, f) == FALSE) {
1156 		    zapinlist(&(ND_other(n)), e);
1157 		    if (d == 1)
1158 			fast_edge(e);
1159 		    /*else unitize(e); ### */
1160 		    i--;
1161 		}
1162 	    }
1163     }
1164 }
1165 
1166 /* make_lrvn:
1167  * Add left and right slacknodes to a cluster which
1168  * are used in the LP to constrain nodes not in g but
1169  * sharing its ranks to be to the left or right of g
1170  * by a specified amount.
1171  * The slacknodes ln and rn give the x position of the
1172  * left and right side of the cluster's bounding box. In
1173  * particular, any cluster labels on the left or right side
1174  * are inside.
1175  * If a cluster has a label, and we have rankdir!=LR, we make
1176  * sure the cluster is wide enough for the label. Note that
1177  * if the label is wider than the cluster, the nodes in the
1178  * cluster may not be centered.
1179  */
make_lrvn(graph_t * g)1180 static void make_lrvn(graph_t * g)
1181 {
1182     node_t *ln, *rn;
1183 
1184     if (GD_ln(g))
1185 	return;
1186     ln = virtual_node(dot_root(g));
1187     ND_node_type(ln) = SLACKNODE;
1188     rn = virtual_node(dot_root(g));
1189     ND_node_type(rn) = SLACKNODE;
1190 
1191     if (GD_label(g) && (g != dot_root(g)) && !GD_flip(agroot(g))) {
1192 	int w = MAX(GD_border(g)[BOTTOM_IX].x, GD_border(g)[TOP_IX].x);
1193 	make_aux_edge(ln, rn, w, 0);
1194     }
1195 
1196     GD_ln(g) = ln;
1197     GD_rn(g) = rn;
1198 }
1199 
1200 /* contain_nodes:
1201  * make left and right bounding box virtual nodes ln and rn
1202  * constrain interior nodes
1203  */
contain_nodes(graph_t * g)1204 static void contain_nodes(graph_t * g)
1205 {
1206     int margin, r;
1207     node_t *ln, *rn, *v;
1208 
1209     margin = late_int (g, G_margin, CL_OFFSET, 0);
1210     make_lrvn(g);
1211     ln = GD_ln(g);
1212     rn = GD_rn(g);
1213     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
1214 	if (GD_rank(g)[r].n == 0)
1215 	    continue;
1216 	v = GD_rank(g)[r].v[0];
1217 	if (v == NULL) {
1218 	    agerr(AGERR, "contain_nodes clust %s rank %d missing node\n",
1219 		  agnameof(g), r);
1220 	    continue;
1221 	}
1222 	make_aux_edge(ln, v,
1223 		      ND_lw(v) + margin + GD_border(g)[LEFT_IX].x, 0);
1224 	v = GD_rank(g)[r].v[GD_rank(g)[r].n - 1];
1225 	make_aux_edge(v, rn,
1226 		      ND_rw(v) + margin + GD_border(g)[RIGHT_IX].x, 0);
1227     }
1228 }
1229 
1230 /* idealsize:
1231  * set g->drawing->size to a reasonable default.
1232  * returns a boolean to indicate if drawing is to
1233  * be scaled and filled */
idealsize(graph_t * g,double minallowed)1234 static boolean idealsize(graph_t * g, double minallowed)
1235 {
1236     double xf, yf, f, R;
1237     pointf b, relpage, margin;
1238 
1239     /* try for one page */
1240     relpage = GD_drawing(g)->page;
1241     if (relpage.x < 0.001 || relpage.y < 0.001)
1242 	return FALSE;		/* no page was specified */
1243     margin = GD_drawing(g)->margin;
1244     relpage = sub_pointf(relpage, margin);
1245     relpage = sub_pointf(relpage, margin);
1246     b.x = GD_bb(g).UR.x;
1247     b.y = GD_bb(g).UR.y;
1248     xf = relpage.x / b.x;
1249     yf = relpage.y / b.y;
1250     if ((xf >= 1.0) && (yf >= 1.0))
1251 	return FALSE;		/* fits on one page */
1252 
1253     f = MIN(xf, yf);
1254     xf = yf = MAX(f, minallowed);
1255 
1256     R = ceil((xf * b.x) / relpage.x);
1257     xf = ((R * relpage.x) / b.x);
1258     R = ceil((yf * b.y) / relpage.y);
1259     yf = ((R * relpage.y) / b.y);
1260     GD_drawing(g)->size.x = b.x * xf;
1261     GD_drawing(g)->size.y = b.y * yf;
1262     return TRUE;
1263 }
1264