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  *  graphics code generator
16  */
17 
18 #include "config.h"
19 
20 #include <string.h>
21 #include <ctype.h>
22 #include <locale.h>
23 #include "render.h"
24 #include "agxbuf.h"
25 #include "htmltable.h"
26 #include "gvc.h"
27 #include "cdt.h"
28 #include "xdot.h"
29 
30 #ifdef _WIN32
31 #define strtok_r strtok_s
32 #endif
33 
34 #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)
35 #define FUZZ 3
36 #define EPSILON .0001
37 
38 typedef struct {
39     xdot_op op;
40     boxf bb;
41     textspan_t* span;
42 } exdot_op;
43 
init_xdot(Agraph_t * g)44 void* init_xdot (Agraph_t* g)
45 {
46     char* p;
47     xdot* xd = NULL;
48 
49     if (!((p = agget(g, "_background")) && p[0])) {
50 	if (!((p = agget(g, "_draw_")) && p[0])) {
51 	    return NULL;
52 	}
53     }
54 #ifdef DEBUG
55     if (Verbose) {
56 	start_timer();
57     }
58 #endif
59     xd = parseXDotF (p, NULL, sizeof (exdot_op));
60 
61     if (!xd) {
62 	agerr(AGWARN, "Could not parse \"_background\" attribute in graph %s\n", agnameof(g));
63 	agerr(AGPREV, "  \"%s\"\n", p);
64     }
65 #ifdef DEBUG
66     if (Verbose) {
67 	xdot_stats stats;
68 	double et = elapsed_sec();
69 	statXDot (xd, &stats);
70 	fprintf (stderr, "%d ops %.2f sec\n", stats.cnt, et);
71 	fprintf (stderr, "%d polygons %d points\n", stats.n_polygon, stats.n_polygon_pts);
72 	fprintf (stderr, "%d polylines %d points\n", stats.n_polyline, stats.n_polyline_pts);
73 	fprintf (stderr, "%d beziers %d points\n", stats.n_bezier, stats.n_bezier_pts);
74 	fprintf (stderr, "%d ellipses\n", stats.n_ellipse);
75 	fprintf (stderr, "%d texts\n", stats.n_text);
76     }
77 #endif
78     return xd;
79 }
80 
81 static char *defaultlinestyle[3] = { "solid\0", "setlinewidth\0001\0", 0 };
82 
83 /* push empty graphic state for current object */
push_obj_state(GVJ_t * job)84 obj_state_t* push_obj_state(GVJ_t *job)
85 {
86     obj_state_t *obj, *parent;
87 
88     if (! (obj = zmalloc(sizeof(obj_state_t))))
89         agerr(AGERR, "no memory from zmalloc()\n");
90 
91     parent = obj->parent = job->obj;
92     job->obj = obj;
93     if (parent) {
94         obj->pencolor = parent->pencolor;        /* default styles to parent's style */
95         obj->fillcolor = parent->fillcolor;
96         obj->pen = parent->pen;
97         obj->fill = parent->fill;
98         obj->penwidth = parent->penwidth;
99 	obj->gradient_angle = parent->gradient_angle;
100 	obj->stopcolor = parent->stopcolor;
101     }
102     else {
103 	/* obj->pencolor = NULL */
104 	/* obj->fillcolor = NULL */
105 	obj->pen = PEN_SOLID;
106 	obj->fill = FILL_NONE;
107 	obj->penwidth = PENWIDTH_NORMAL;
108     }
109     return obj;
110 }
111 
112 /* pop graphic state of current object */
pop_obj_state(GVJ_t * job)113 void pop_obj_state(GVJ_t *job)
114 {
115     obj_state_t *obj = job->obj;
116 
117     assert(obj);
118 
119     free(obj->id);
120     free(obj->url);
121     free(obj->labelurl);
122     free(obj->tailurl);
123     free(obj->headurl);
124     free(obj->tooltip);
125     free(obj->labeltooltip);
126     free(obj->tailtooltip);
127     free(obj->headtooltip);
128     free(obj->target);
129     free(obj->labeltarget);
130     free(obj->tailtarget);
131     free(obj->headtarget);
132     free(obj->url_map_p);
133     free(obj->url_bsplinemap_p);
134     free(obj->url_bsplinemap_n);
135 
136     job->obj = obj->parent;
137     free(obj);
138 }
139 
140 /* initMapData:
141  * Store image map data into job, substituting for node, edge, etc.
142  * names.
143  * Return 1 if an assignment was made for url or tooltip or target.
144  */
145 int
initMapData(GVJ_t * job,char * lbl,char * url,char * tooltip,char * target,char * id,void * gobj)146 initMapData (GVJ_t* job, char* lbl, char* url, char* tooltip, char* target, char *id,
147   void* gobj)
148 {
149     obj_state_t *obj = job->obj;
150     int flags = job->flags;
151     int assigned = 0;
152 
153     if ((flags & GVRENDER_DOES_LABELS) && lbl)
154         obj->label = lbl;
155     if (flags & GVRENDER_DOES_MAPS) {
156         obj->id = strdup_and_subst_obj(id, gobj);
157 	if (url && url[0]) {
158             obj->url = strdup_and_subst_obj(url, gobj);
159 	    assigned = 1;
160         }
161     }
162     if (flags & GVRENDER_DOES_TOOLTIPS) {
163         if (tooltip && tooltip[0]) {
164             obj->tooltip = strdup_and_subst_obj(tooltip, gobj);
165             obj->explicit_tooltip = TRUE;
166 	    assigned = 1;
167         }
168         else if (obj->label) {
169             obj->tooltip = strdup(obj->label);
170 	    assigned = 1;
171         }
172     }
173     if ((flags & GVRENDER_DOES_TARGETS) && target && target[0]) {
174         obj->target = strdup_and_subst_obj(target, gobj);
175 	assigned = 1;
176     }
177     return assigned;
178 }
179 
180 static void
layerPagePrefix(GVJ_t * job,agxbuf * xb)181 layerPagePrefix (GVJ_t* job, agxbuf* xb)
182 {
183     char buf[128]; /* large enough for 2 decimal 64-bit ints and "page_," */
184     if (job->layerNum > 1 && (job->flags & GVDEVICE_DOES_LAYERS)) {
185 	agxbput (xb, job->gvc->layerIDs[job->layerNum]);
186 	agxbputc (xb, '_');
187     }
188     if ((job->pagesArrayElem.x > 0) || (job->pagesArrayElem.y > 0)) {
189 	sprintf (buf, "page%d,%d_", job->pagesArrayElem.x, job->pagesArrayElem.y);
190 	agxbput (xb, buf);
191     }
192 }
193 
194 /* genObjId:
195  * Use id of root graph if any, plus kind and internal id of object
196  */
197 char*
getObjId(GVJ_t * job,void * obj,agxbuf * xb)198 getObjId (GVJ_t* job, void* obj, agxbuf* xb)
199 {
200     char* id;
201     graph_t* root = job->gvc->g;
202     char* gid = GD_drawing(root)->id;
203     long idnum = 0;
204     char* pfx = NULL;
205     char buf[64]; /* large enough for a decimal 64-bit int */
206 
207     layerPagePrefix (job, xb);
208 
209     id = agget(obj, "id");
210     if (id && (*id != '\0')) {
211 	agxbput (xb, id);
212 	return agxbuse(xb);
213     }
214 
215     if ((obj != root) && gid) {
216 	agxbput (xb, gid);
217 	agxbputc (xb, '_');
218     }
219 
220     switch (agobjkind(obj)) {
221     case AGRAPH:
222 	idnum = AGSEQ(obj);
223 	if (root == obj)
224 	    pfx = "graph";
225 	else
226 	    pfx = "clust";
227 	break;
228     case AGNODE:
229         idnum = AGSEQ((Agnode_t*)obj);
230 	pfx = "node";
231 	break;
232     case AGEDGE:
233         idnum = AGSEQ((Agedge_t*)obj);
234 	pfx = "edge";
235 	break;
236     }
237 
238     agxbput (xb, pfx);
239     sprintf (buf, "%ld", idnum);
240     agxbput (xb, buf);
241 
242     return agxbuse(xb);
243 }
244 
245 /* interpretCRNL:
246  * Map "\n" to ^J, "\r" to ^M and "\l" to ^J.
247  * Map "\\" to backslash.
248  * Map "\x" to x.
249  * Mapping is done in place.
250  * Return input string.
251  */
252 
253 static char*
interpretCRNL(char * ins)254 interpretCRNL (char* ins)
255 {
256     char* rets = ins;
257     char* outs = ins;
258     char c;
259     boolean backslash_seen = FALSE;
260 
261     while ((c = *ins++)) {
262 	if (backslash_seen) {
263 	    switch (c) {
264 	    case 'n' :
265 	    case 'l' :
266 		*outs++ = '\n';
267 		break;
268 	    case 'r' :
269 		*outs++ = '\r';
270 		break;
271 	    default :
272 		*outs++ = c;
273 		break;
274 	    }
275 	    backslash_seen = FALSE;
276 	}
277 	else {
278 	    if (c == '\\')
279 		backslash_seen = TRUE;
280 	    else
281 		*outs++ = c;
282 	}
283     }
284     *outs = '\0';
285     return rets;
286 }
287 
288 /* preprocessTooltip:
289  * Tooltips are a weak form of escString, so we expect object substitution
290  * and newlines to be handled. The former occurs in initMapData. Here we
291  * map "\r", "\l" and "\n" to newlines. (We don't try to handle alignment
292  * as in real labels.) To make things uniform when the
293  * tooltip is emitted latter as visible text, we also convert HTML escape
294  * sequences into UTF8. This is already occurring when tooltips are input
295  * via HTML-like tables.
296  */
297 static char*
preprocessTooltip(char * s,void * gobj)298 preprocessTooltip(char* s, void* gobj)
299 {
300     Agraph_t* g = agroot(gobj);
301     int charset = GD_charset(g);
302     char* news;
303     switch (charset) {
304 	case CHAR_LATIN1:
305 	    news = latin1ToUTF8(s);
306 	    break;
307 	default: /* UTF8 */
308 	    news = htmlEntityUTF8(s, g);
309 	    break;
310     }
311 
312     return interpretCRNL (news);
313 }
314 
315 static void
initObjMapData(GVJ_t * job,textlabel_t * lab,void * gobj)316 initObjMapData (GVJ_t* job, textlabel_t *lab, void* gobj)
317 {
318     char* lbl;
319     char* url = agget(gobj, "href");
320     char* tooltip = agget(gobj, "tooltip");
321     char* target = agget(gobj, "target");
322     char* id;
323     unsigned char buf[SMALLBUF];
324     agxbuf xb;
325 
326     agxbinit(&xb, SMALLBUF, buf);
327 
328     if (lab) lbl = lab->text;
329     else lbl = NULL;
330     if (!url || !*url)  /* try URL as an alias for href */
331 	url = agget(gobj, "URL");
332     id = getObjId (job, gobj, &xb);
333     if (tooltip)
334 	tooltip = preprocessTooltip (tooltip, gobj);
335     initMapData (job, lbl, url, tooltip, target, id, gobj);
336 
337     free (tooltip);
338     agxbfree(&xb);
339 }
340 
map_point(GVJ_t * job,pointf pf)341 static void map_point(GVJ_t *job, pointf pf)
342 {
343     obj_state_t *obj = job->obj;
344     int flags = job->flags;
345     pointf *p;
346 
347     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
348 	if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
349 	    obj->url_map_shape = MAP_RECTANGLE;
350 	    obj->url_map_n = 2;
351 	}
352 	else {
353 	    obj->url_map_shape = MAP_POLYGON;
354 	    obj->url_map_n = 4;
355 	}
356 	free(obj->url_map_p);
357 	obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
358 	P2RECT(pf, p, FUZZ, FUZZ);
359 	if (! (flags & GVRENDER_DOES_TRANSFORM))
360 	    gvrender_ptf_A(job, p, p, 2);
361 	if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
362 	    rect2poly(p);
363     }
364 }
365 
checkClusterStyle(graph_t * sg,int * flagp)366 static char **checkClusterStyle(graph_t* sg, int *flagp)
367 {
368     char *style;
369     char **pstyle = 0;
370     int istyle = 0;
371 
372     if (((style = agget(sg, "style")) != 0) && style[0]) {
373 	char **pp;
374 	char **qp;
375 	char *p;
376 	pp = pstyle = parse_style(style);
377 	while ((p = *pp)) {
378 	    if (strcmp(p, "filled") == 0) {
379 		istyle |= FILLED;
380 		pp++;
381  	    }else if (strcmp(p, "radial") == 0) {
382  		istyle |= (FILLED | RADIAL);
383 		qp = pp; /* remove rounded from list passed to renderer */
384 		do {
385 		    qp++;
386 		    *(qp-1) = *qp;
387 		} while (*qp);
388  	    }else if (strcmp(p, "striped") == 0) {
389  		istyle |= STRIPED;
390 		qp = pp; /* remove rounded from list passed to renderer */
391 		do {
392 		    qp++;
393 		    *(qp-1) = *qp;
394 		} while (*qp);
395 	    }else if (strcmp(p, "rounded") == 0) {
396 		istyle |= ROUNDED;
397 		qp = pp; /* remove rounded from list passed to renderer */
398 		do {
399 		    qp++;
400 		    *(qp-1) = *qp;
401 		} while (*qp);
402 	    } else pp++;
403 	}
404     }
405 
406     *flagp = istyle;
407     return pstyle;
408 }
409 
410 typedef struct {
411     char* color;   /* segment color */
412     float t;       /* segment size >= 0 */
413     boolean hasFraction;  /* true if color explicitly specifies its fraction */
414 } colorseg_t;
415 /* Sum of segment sizes should add to 1 */
416 typedef struct {
417     int numc;     /* number of used segments in segs; may include segs with t == 0 */
418     char* base;   /* storage of color names */
419     colorseg_t* segs;  /* array of segments; real segments always followed by a sentinel */
420 } colorsegs_t;
421 
422 static void
freeSegs(colorsegs_t * segs)423 freeSegs (colorsegs_t* segs)
424 {
425     free (segs->base);
426     free (segs->segs);
427     free (segs);
428 }
429 
430 /* getSegLen:
431  * Find semicolon in s, replace with '\0'.
432  * Convert remainder to float v.
433  * Return 0 if no float given
434  * Return -1 on failure
435  */
getSegLen(char * s)436 static double getSegLen (char* s)
437 {
438     char* p = strchr (s, ';');
439     char* endp;
440     double v;
441 
442     if (!p) {
443 	return 0;
444     }
445     *p++ = '\0';
446     v = strtod (p, &endp);
447     if (endp != p) {  /* scanned something */
448 	if (v >= 0)
449 	    return v;
450     }
451     return -1;
452 }
453 
454 #define EPS 1E-5
455 #define AEQ0(x) (((x) < EPS) && ((x) > -EPS))
456 
457 /* parseSegs:
458  * Parse string of form color;float:color;float:...:color;float:color
459  * where the semicolon-floats are optional, nonnegative, sum to <= 1.
460  * Store the values in an array of colorseg_t's and return the array in psegs.
461  * If nseg == 0, count the number of colors.
462  * If the sum of the floats does not equal 1, the remainder is equally distributed
463  * to all colors without an explicit float. If no such colors exist, the remainder
464  * is added to the last color.
465  *  0 => okay
466  *  1 => error without message
467  *  2 => error with message
468  *  3 => warning message
469  * There is a last sentinel segment with color == NULL; it will always follow
470  * the last segment with t > 0.
471  *
472  * Note that psegs is only assigned to if the return value is 0 or 3.
473  * Otherwise, psegs is left unchanged and the allocated memory is
474  * freed before returning.
475  */
476 static int
parseSegs(char * clrs,int nseg,colorsegs_t ** psegs)477 parseSegs (char* clrs, int nseg, colorsegs_t** psegs)
478 {
479     colorsegs_t* segs = NEW(colorsegs_t);
480     colorseg_t* s;
481     char* colors = strdup (clrs);
482     char* color;
483     int cnum = 0;
484     double v, left = 1;
485     static int doWarn = 1;
486     int i, rval = 0;
487     char* p;
488 
489     if (nseg == 0) {
490 	nseg = 1;
491 	/* need to know how many colors separated by ':' */
492 	for (p = colors; *p; p++) {
493 	    if (*p == ':') nseg++;
494 	}
495     }
496 
497     segs->base = colors;
498     segs->segs = s = N_NEW(nseg+1,colorseg_t);
499     for (color = strtok(colors, ":"); color; color = strtok(0, ":")) {
500 	if ((v = getSegLen (color)) >= 0) {
501 	    double del = v - left;
502 	    if (del > 0) {
503 		if (doWarn && !AEQ0(del)) {
504 		    agerr (AGWARN, "Total size > 1 in \"%s\" color spec ", clrs);
505 		    doWarn = 0;
506 		    rval = 3;
507 		}
508 		v = left;
509 	    }
510 	    left -= v;
511 	    if (v > 0) s[cnum].hasFraction = TRUE;
512 	    if (*color) s[cnum].color = color;
513 	    s[cnum++].t = v;
514 	}
515 	else {
516 	    if (doWarn) {
517 		agerr (AGERR, "Illegal value in \"%s\" color attribute; float expected after ';'\n",
518                     clrs);
519 		doWarn = 0;
520 		rval = 2;
521 	    }
522 	    else rval = 1;
523 	    freeSegs (segs);
524 	    return rval;
525 	}
526 	if (AEQ0(left)) {
527 	    left = 0;
528 	    break;
529 	}
530     }
531 
532     /* distribute remaining into slot with t == 0; if none, add to last */
533     if (left > 0) {
534 	/* count zero segments */
535 	nseg = 0;
536 	for (i = 0; i < cnum; i++) {
537 	    if (s[i].t == 0) nseg++;
538 	}
539 	if (nseg > 0) {
540 	    double delta = left/nseg;
541 	    for (i = 0; i < cnum; i++) {
542 		if (s[i].t == 0) s[i].t = delta;
543 	    }
544 	}
545 	else {
546 	    s[cnum-1].t += left;
547 	}
548     }
549 
550     /* Make sure last positive segment is followed by a sentinel. */
551     nseg = 0;
552     for (i = cnum-1; i >= 0; i--) {
553 	if (s[i].t > 0) break;
554     }
555     s[i+1].color = NULL;
556     segs->numc = i+1;
557 
558     *psegs = segs;
559     return rval;
560 }
561 
562 #define THIN_LINE 0.5
563 
564 /* wedgedEllipse:
565  * Fill an ellipse whose bounding box is given by 2 points in pf
566  * with multiple wedges determined by the color spec in clrs.
567  * clrs is a list of colon separated colors, with possible quantities.
568  * Thin boundaries are drawn.
569  *  0 => okay
570  *  1 => error without message
571  *  2 => error with message
572  *  3 => warning message
573  */
574 int
wedgedEllipse(GVJ_t * job,pointf * pf,char * clrs)575 wedgedEllipse (GVJ_t* job, pointf * pf, char* clrs)
576 {
577     colorsegs_t* segs;
578     colorseg_t* s;
579     int rv;
580     double save_penwidth = job->obj->penwidth;
581     pointf ctr, semi;
582     Ppolyline_t* pp;
583     double angle0, angle1;
584 
585     rv = parseSegs (clrs, 0, &segs);
586     if ((rv == 1) || (rv == 2)) return rv;
587     ctr.x = (pf[0].x + pf[1].x) / 2.;
588     ctr.y = (pf[0].y + pf[1].y) / 2.;
589     semi.x = pf[1].x - ctr.x;
590     semi.y = pf[1].y - ctr.y;
591     if (save_penwidth > THIN_LINE)
592 	gvrender_set_penwidth(job, THIN_LINE);
593 
594     angle0 = 0;
595     for (s = segs->segs; s->color; s++) {
596 	if (s->t == 0) continue;
597 	gvrender_set_fillcolor (job, (s->color?s->color:DEFAULT_COLOR));
598 
599 	if (s[1].color == NULL)
600 	    angle1 = 2*M_PI;
601 	else
602 	    angle1 = angle0 + 2*M_PI*(s->t);
603 	pp = ellipticWedge (ctr, semi.x, semi.y, angle0, angle1);
604 	gvrender_beziercurve(job, pp->ps, pp->pn, 0, 0, 1);
605 	angle0 = angle1;
606 	freePath (pp);
607     }
608 
609     if (save_penwidth > THIN_LINE)
610 	gvrender_set_penwidth(job, save_penwidth);
611     freeSegs (segs);
612     return rv;
613 }
614 
615 /* stripedBox:
616  * Fill a rectangular box with vertical stripes of colors.
617  * AF gives 4 corner points, with AF[0] the LL corner and the points ordered CCW.
618  * clrs is a list of colon separated colors, with possible quantities.
619  * Thin boundaries are drawn.
620  *  0 => okay
621  *  1 => error without message
622  *  2 => error with message
623  *  3 => warning message
624  */
625 int
stripedBox(GVJ_t * job,pointf * AF,char * clrs,int rotate)626 stripedBox (GVJ_t * job, pointf* AF, char* clrs, int rotate)
627 {
628     colorsegs_t* segs;
629     colorseg_t* s;
630     int rv;
631     double xdelta;
632     pointf pts[4];
633     double lastx;
634     double save_penwidth = job->obj->penwidth;
635 
636     rv = parseSegs (clrs, 0, &segs);
637     if ((rv == 1) || (rv == 2)) return rv;
638     if (rotate) {
639 	pts[0] = AF[2];
640 	pts[1] = AF[3];
641 	pts[2] = AF[0];
642 	pts[3] = AF[1];
643     } else {
644 	pts[0] = AF[0];
645 	pts[1] = AF[1];
646 	pts[2] = AF[2];
647 	pts[3] = AF[3];
648     }
649     lastx = pts[1].x;
650     xdelta = (pts[1].x - pts[0].x);
651     pts[1].x = pts[2].x = pts[0].x;
652 
653     if (save_penwidth > THIN_LINE)
654 	gvrender_set_penwidth(job, THIN_LINE);
655     for (s = segs->segs; s->color; s++) {
656 	if (s->t == 0) continue;
657 	gvrender_set_fillcolor (job, (s->color?s->color:DEFAULT_COLOR));
658 	/* gvrender_polygon(job, pts, 4, FILL | NO_POLY); */
659 	if (s[1].color == NULL)
660 	    pts[1].x = pts[2].x = lastx;
661 	else
662 	    pts[1].x = pts[2].x = pts[0].x + xdelta*(s->t);
663 	gvrender_polygon(job, pts, 4, FILL);
664 	pts[0].x = pts[3].x = pts[1].x;
665     }
666     if (save_penwidth > THIN_LINE)
667 	gvrender_set_penwidth(job, save_penwidth);
668     freeSegs (segs);
669     return rv;
670 }
671 
emit_map_rect(GVJ_t * job,boxf b)672 void emit_map_rect(GVJ_t *job, boxf b)
673 {
674     obj_state_t *obj = job->obj;
675     int flags = job->flags;
676     pointf *p;
677 
678     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
679 	if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
680 	    obj->url_map_shape = MAP_RECTANGLE;
681 	    obj->url_map_n = 2;
682 	}
683 	else {
684 	    obj->url_map_shape = MAP_POLYGON;
685 	    obj->url_map_n = 4;
686 	}
687 	free(obj->url_map_p);
688 	obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
689 	p[0] = b.LL;
690 	p[1] = b.UR;
691 	if (! (flags & GVRENDER_DOES_TRANSFORM))
692 	    gvrender_ptf_A(job, p, p, 2);
693 	if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
694 	    rect2poly(p);
695     }
696 }
697 
map_label(GVJ_t * job,textlabel_t * lab)698 static void map_label(GVJ_t *job, textlabel_t *lab)
699 {
700     obj_state_t *obj = job->obj;
701     int flags = job->flags;
702     pointf *p;
703 
704     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
705 	if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
706 	    obj->url_map_shape = MAP_RECTANGLE;
707 	    obj->url_map_n = 2;
708 	}
709 	else {
710 	    obj->url_map_shape = MAP_POLYGON;
711 	    obj->url_map_n = 4;
712 	}
713 	free(obj->url_map_p);
714 	obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
715 	P2RECT(lab->pos, p, lab->dimen.x / 2., lab->dimen.y / 2.);
716 	if (! (flags & GVRENDER_DOES_TRANSFORM))
717 	    gvrender_ptf_A(job, p, p, 2);
718 	if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
719 	    rect2poly(p);
720     }
721 }
722 
723 /* isRect:
724  * isRect function returns true when polygon has
725  * regular rectangular shape. Rectangle is regular when
726  * it is not skewed and distorted and orientation is almost zero
727  */
isRect(polygon_t * p)728 static boolean isRect(polygon_t * p)
729 {
730     return (p->sides == 4 && (ROUND(p->orientation) % 90) == 0
731             && p->distortion == 0.0 && p->skew == 0.0);
732 }
733 
734 /*
735  * isFilled function returns 1 if filled style has been set for node 'n'
736  * otherwise returns 0. it accepts pointer to node_t as an argument
737  */
ifFilled(node_t * n)738 static int ifFilled(node_t * n)
739 {
740     char *style, *p, **pp;
741     int r = 0;
742     style = late_nnstring(n, N_style, "");
743     if (style[0]) {
744         pp = parse_style(style);
745         while ((p = *pp)) {
746             if (strcmp(p, "filled") == 0)
747                 r = 1;
748             pp++;
749         }
750     }
751     return r;
752 }
753 
754 /* pEllipse:
755  * pEllipse function returns 'np' points from the circumference
756  * of ellipse described by radii 'a' and 'b'.
757  * Assumes 'np' is greater than zero.
758  * 'np' should be at least 4 to sample polygon from ellipse
759  */
pEllipse(double a,double b,int np)760 static pointf *pEllipse(double a, double b, int np)
761 {
762     double theta = 0.0;
763     double deltheta = 2 * M_PI / np;
764     int i;
765     pointf *ps;
766 
767     ps = N_NEW(np, pointf);
768     for (i = 0; i < np; i++) {
769         ps[i].x = a * cos(theta);
770         ps[i].y = b * sin(theta);
771         theta += deltheta;
772     }
773     return ps;
774 }
775 
776 #define HW 2.0   /* maximum distance away from line, in points */
777 
778 /* check_control_points:
779  * check_control_points function checks the size of quadrilateral
780  * formed by four control points
781  * returns 1 if four points are in line (or close to line)
782  * else return 0
783  */
check_control_points(pointf * cp)784 static int check_control_points(pointf *cp)
785 {
786     double dis1 = ptToLine2 (cp[0], cp[3], cp[1]);
787     double dis2 = ptToLine2 (cp[0], cp[3], cp[2]);
788     if (dis1 < HW*HW && dis2 < HW*HW)
789         return 1;
790     else
791         return 0;
792 }
793 
794 /* update bounding box to contain a bezier segment */
update_bb_bz(boxf * bb,pointf * cp)795 void update_bb_bz(boxf *bb, pointf *cp)
796 {
797 
798     /* if any control point of the segment is outside the bounding box */
799     if (cp[0].x > bb->UR.x || cp[0].x < bb->LL.x ||
800         cp[0].y > bb->UR.y || cp[0].y < bb->LL.y ||
801         cp[1].x > bb->UR.x || cp[1].x < bb->LL.x ||
802         cp[1].y > bb->UR.y || cp[1].y < bb->LL.y ||
803         cp[2].x > bb->UR.x || cp[2].x < bb->LL.x ||
804         cp[2].y > bb->UR.y || cp[2].y < bb->LL.y ||
805         cp[3].x > bb->UR.x || cp[3].x < bb->LL.x ||
806         cp[3].y > bb->UR.y || cp[3].y < bb->LL.y) {
807 
808         /* if the segment is sufficiently refined */
809         if (check_control_points(cp)) {
810             int i;
811             /* expand the bounding box */
812             for (i = 0; i < 4; i++) {
813                 if (cp[i].x > bb->UR.x)
814                     bb->UR.x = cp[i].x;
815                 else if (cp[i].x < bb->LL.x)
816                     bb->LL.x = cp[i].x;
817                 if (cp[i].y > bb->UR.y)
818                     bb->UR.y = cp[i].y;
819                 else if (cp[i].y < bb->LL.y)
820                     bb->LL.y = cp[i].y;
821             }
822         }
823         else { /* else refine the segment */
824             pointf left[4], right[4];
825             Bezier (cp, 3, 0.5, left, right);
826             update_bb_bz(bb, left);
827             update_bb_bz(bb, right);
828         }
829     }
830 }
831 
832 #if (DEBUG==2)
psmapOutput(pointf * ps,int n)833 static void psmapOutput (pointf* ps, int n)
834 {
835    int i;
836    fprintf (stdout, "newpath %f %f moveto\n", ps[0].x, ps[0].y);
837    for (i=1; i < n; i++)
838         fprintf (stdout, "%f %f lineto\n", ps[i].x, ps[i].y);
839    fprintf (stdout, "closepath stroke\n");
840 }
841 #endif
842 
843 typedef struct segitem_s {
844     pointf p;
845     struct segitem_s* next;
846 } segitem_t;
847 
848 #define MARK_FIRST_SEG(L) ((L)->next = (segitem_t*)1)
849 #define FIRST_SEG(L) ((L)->next == (segitem_t*)1)
850 #define INIT_SEG(P,L) {(L)->next = 0; (L)->p = P;}
851 
appendSeg(pointf p,segitem_t * lp)852 static segitem_t* appendSeg (pointf p, segitem_t* lp)
853 {
854     segitem_t* s = GNEW(segitem_t);
855     INIT_SEG (p, s);
856     lp->next = s;
857     return s;
858 }
859 
860 /* map_bspline_poly:
861  * Output the polygon determined by the n points in p1, followed
862  * by the n points in p2 in reverse order. Assumes n <= 50.
863  */
map_bspline_poly(pointf ** pbs_p,int ** pbs_n,int * pbs_poly_n,int n,pointf * p1,pointf * p2)864 static void map_bspline_poly(pointf **pbs_p, int **pbs_n, int *pbs_poly_n, int n, pointf* p1, pointf* p2)
865 {
866     int i = 0, nump = 0, last = 2*n-1;
867 
868     for ( ; i < *pbs_poly_n; i++)
869         nump += (*pbs_n)[i];
870 
871     (*pbs_poly_n)++;
872     *pbs_n = grealloc(*pbs_n, (*pbs_poly_n) * sizeof(int));
873     (*pbs_n)[i] = 2*n;
874     *pbs_p = grealloc(*pbs_p, (nump + 2*n) * sizeof(pointf));
875 
876     for (i = 0; i < n; i++) {
877         (*pbs_p)[nump+i] = p1[i];
878         (*pbs_p)[nump+last-i] = p2[i];
879     }
880 #if (DEBUG==2)
881     psmapOutput (*pbs_p + nump, last+1);
882 #endif
883 }
884 
885 /* approx_bezier:
886  * Approximate Bezier by line segments. If the four points are
887  * almost colinear, as determined by check_control_points, we store
888  * the segment cp[0]-cp[3]. Otherwise we split the Bezier into 2 and recurse.
889  * Since 2 contiguous segments share an endpoint, we actually store
890  * the segments as a list of points.
891  * New points are appended to the list given by lp. The tail of the
892  * list is returned.
893  */
approx_bezier(pointf * cp,segitem_t * lp)894 static segitem_t* approx_bezier (pointf *cp, segitem_t* lp)
895 {
896     pointf left[4], right[4];
897 
898     if (check_control_points(cp)) {
899         if (FIRST_SEG (lp)) INIT_SEG (cp[0], lp);
900         lp = appendSeg (cp[3], lp);
901     }
902     else {
903         Bezier (cp, 3, 0.5, left, right);
904         lp = approx_bezier (left, lp);
905         lp = approx_bezier (right, lp);
906     }
907     return lp;
908 }
909 
910 /* bisect:
911  * Return the angle of the bisector between the two rays
912  * pp-cp and cp-np. The bisector returned is always to the
913  * left of pp-cp-np.
914  */
bisect(pointf pp,pointf cp,pointf np)915 static double bisect (pointf pp, pointf cp, pointf np)
916 {
917   double ang, theta, phi;
918   theta = atan2(np.y - cp.y,np.x - cp.x);
919   phi = atan2(pp.y - cp.y,pp.x - cp.x);
920   ang = theta - phi;
921   if (ang > 0) ang -= 2*M_PI;
922 
923   return (phi + ang/2.0);
924 }
925 
926 /* mkSegPts:
927  * Determine polygon points related to 2 segments prv-cur and cur-nxt.
928  * The points lie on the bisector of the 2 segments, passing through cur,
929  * and distance w2 from cur. The points are stored in p1 and p2.
930  * If p1 is NULL, we use the normal to cur-nxt.
931  * If p2 is NULL, we use the normal to prv-cur.
932  * Assume at least one of prv or nxt is non-NULL.
933  */
mkSegPts(segitem_t * prv,segitem_t * cur,segitem_t * nxt,pointf * p1,pointf * p2,double w2)934 static void mkSegPts (segitem_t* prv, segitem_t* cur, segitem_t* nxt,
935         pointf* p1, pointf* p2, double w2)
936 {
937     pointf cp, pp, np;
938     double theta, delx, dely;
939     pointf p;
940 
941     cp = cur->p;
942     /* if prv or nxt are NULL, use the one given to create a collinear
943      * prv or nxt. This could be more efficiently done with special case code,
944      * but this way is more uniform.
945      */
946     if (prv) {
947         pp = prv->p;
948         if (nxt)
949             np = nxt->p;
950         else {
951             np.x = 2*cp.x - pp.x;
952             np.y = 2*cp.y - pp.y;
953         }
954     }
955     else {
956         np = nxt->p;
957         pp.x = 2*cp.x - np.x;
958         pp.y = 2*cp.y - np.y;
959     }
960     theta = bisect(pp,cp,np);
961     delx = w2*cos(theta);
962     dely = w2*sin(theta);
963     p.x = cp.x + delx;
964     p.y = cp.y + dely;
965     *p1 = p;
966     p.x = cp.x - delx;
967     p.y = cp.y - dely;
968     *p2 = p;
969 }
970 
971 /* map_output_bspline:
972  * Construct and output a closed polygon approximating the input
973  * B-spline bp. We do this by first approximating bp by a sequence
974  * of line segments. We then use the sequence of segments to determine
975  * the polygon.
976  * In cmapx, polygons are limited to 100 points, so we output polygons
977  * in chunks of 100.
978  */
map_output_bspline(pointf ** pbs,int ** pbs_n,int * pbs_poly_n,bezier * bp,double w2)979 static void map_output_bspline (pointf **pbs, int **pbs_n, int *pbs_poly_n, bezier* bp, double w2)
980 {
981     segitem_t* segl = GNEW(segitem_t);
982     segitem_t* segp = segl;
983     segitem_t* segprev;
984     segitem_t* segnext;
985     int nc, j, k, cnt;
986     pointf pts[4], pt1[50], pt2[50];
987 
988     MARK_FIRST_SEG(segl);
989     nc = (bp->size - 1)/3; /* nc is number of bezier curves */
990     for (j = 0; j < nc; j++) {
991         for (k = 0; k < 4; k++) {
992             pts[k] = bp->list[3*j + k];
993         }
994         segp = approx_bezier (pts, segp);
995     }
996 
997     segp = segl;
998     segprev = 0;
999     cnt = 0;
1000     while (segp) {
1001         segnext = segp->next;
1002         mkSegPts (segprev, segp, segnext, pt1+cnt, pt2+cnt, w2);
1003         cnt++;
1004         if ((segnext == NULL) || (cnt == 50)) {
1005             map_bspline_poly (pbs, pbs_n, pbs_poly_n, cnt, pt1, pt2);
1006             pt1[0] = pt1[cnt-1];
1007             pt2[0] = pt2[cnt-1];
1008             cnt = 1;
1009         }
1010         segprev = segp;
1011         segp = segnext;
1012     }
1013 
1014     /* free segl */
1015     while (segl) {
1016         segp = segl->next;
1017         free (segl);
1018         segl = segp;
1019     }
1020 }
1021 
is_natural_number(char * sstr)1022 static boolean is_natural_number(char *sstr)
1023 {
1024     unsigned char *str = (unsigned char *) sstr;
1025 
1026     while (*str)
1027 	if (NOT(isdigit(*str++)))
1028 	    return FALSE;
1029     return TRUE;
1030 }
1031 
layer_index(GVC_t * gvc,char * str,int all)1032 static int layer_index(GVC_t *gvc, char *str, int all)
1033 {
1034     /* GVJ_t *job = gvc->job; */
1035     int i;
1036 
1037     if (streq(str, "all"))
1038 	return all;
1039     if (is_natural_number(str))
1040 	return atoi(str);
1041     if (gvc->layerIDs)
1042 	for (i = 1; i <= gvc->numLayers; i++)
1043 	    if (streq(str, gvc->layerIDs[i]))
1044 		return i;
1045     return -1;
1046 }
1047 
selectedLayer(GVC_t * gvc,int layerNum,int numLayers,char * spec)1048 static boolean selectedLayer(GVC_t *gvc, int layerNum, int numLayers, char *spec)
1049 {
1050     int n0, n1;
1051     unsigned char buf[SMALLBUF];
1052     char *w0, *w1;
1053     char *buf_part_p = NULL, *buf_p = NULL, *cur, *part_in_p;
1054     agxbuf xb;
1055     boolean rval = FALSE;
1056 
1057     agxbinit(&xb, SMALLBUF, buf);
1058     agxbput(&xb, spec);
1059     part_in_p = agxbuse(&xb);
1060 
1061     /* Thanks to Matteo Nastasi for this extended code. */
1062     while ((rval == FALSE) && (cur = strtok_r(part_in_p, gvc->layerListDelims, &buf_part_p))) {
1063 	w1 = w0 = strtok_r (cur, gvc->layerDelims, &buf_p);
1064 	if (w0)
1065 	    w1 = strtok_r (NULL, gvc->layerDelims, &buf_p);
1066 	switch ((w0 != NULL) + (w1 != NULL)) {
1067 	case 0:
1068 	    rval = FALSE;
1069 	    break;
1070 	case 1:
1071 	    n0 = layer_index(gvc, w0, layerNum);
1072 	    rval = (n0 == layerNum);
1073 	    break;
1074 	case 2:
1075 	    n0 = layer_index(gvc, w0, 0);
1076 	    n1 = layer_index(gvc, w1, numLayers);
1077 	    if ((n0 >= 0) || (n1 >= 0)) {
1078 		if (n0 > n1) {
1079 		    int t = n0;
1080 		    n0 = n1;
1081 		    n1 = t;
1082 		}
1083 		rval = BETWEEN(n0, layerNum, n1);
1084 	    }
1085 	    break;
1086 	}
1087 	part_in_p = NULL;
1088     }
1089     agxbfree(&xb);
1090     return rval;
1091 }
1092 
selectedlayer(GVJ_t * job,char * spec)1093 static boolean selectedlayer(GVJ_t *job, char *spec)
1094 {
1095     return selectedLayer (job->gvc, job->layerNum, job->numLayers, spec);
1096 }
1097 
1098 /* parse_layerselect:
1099  * Parse the graph's layerselect attribute, which determines
1100  * which layers are emitted. The specification is the same used
1101  * by the layer attribute.
1102  *
1103  * If we find n layers, we return an array arr of n+2 ints. arr[0]=n.
1104  * arr[n+1]=numLayers+1, acting as a sentinel. The other entries give
1105  * the desired layer indices.
1106  *
1107  * If no layers are detected, NULL is returned.
1108  *
1109  * This implementation does a linear walk through each layer index and
1110  * uses selectedLayer to match it against p. There is probably a more
1111  * efficient way to do this, but this is simple and until we find people
1112  * using huge numbers of layers, it should be adequate.
1113  */
parse_layerselect(GVC_t * gvc,graph_t * g,char * p)1114 static int* parse_layerselect(GVC_t *gvc, graph_t * g, char *p)
1115 {
1116     int* laylist = N_GNEW(gvc->numLayers+2,int);
1117     int i, cnt = 0;
1118     for (i = 1; i <=gvc->numLayers; i++) {
1119 	if (selectedLayer (gvc, i, gvc->numLayers, p)) {
1120 	    laylist[++cnt] = i;
1121 	}
1122     }
1123     if (cnt) {
1124 	laylist[0] = cnt;
1125 	laylist[cnt+1] = gvc->numLayers+1;
1126     }
1127     else {
1128 	agerr(AGWARN, "The layerselect attribute \"%s\" does not match any layer specifed by the layers attribute - ignored.\n", p);
1129 	laylist[0] = cnt;
1130 	free (laylist);
1131 	laylist = NULL;
1132     }
1133     return laylist;
1134 }
1135 
1136 /* parse_layers:
1137  * Split input string into tokens, with separators specified by
1138  * the layersep attribute. Store the values in the gvc->layerIDs array,
1139  * starting at index 1, and return the count.
1140  * Free previously stored list. Note that there is no mechanism
1141  * to free the memory before exit.
1142  */
parse_layers(GVC_t * gvc,graph_t * g,char * p)1143 static int parse_layers(GVC_t *gvc, graph_t * g, char *p)
1144 {
1145     int ntok;
1146     char *tok;
1147     int sz;
1148 
1149     gvc->layerDelims = agget(g, "layersep");
1150     if (!gvc->layerDelims)
1151         gvc->layerDelims = DEFAULT_LAYERSEP;
1152     gvc->layerListDelims = agget(g, "layerlistsep");
1153     if (!gvc->layerListDelims)
1154         gvc->layerListDelims = DEFAULT_LAYERLISTSEP;
1155     if ((tok = strpbrk (gvc->layerDelims, gvc->layerListDelims))) { /* conflict in delimiter strings */
1156 	agerr(AGWARN, "The character \'%c\' appears in both the layersep and layerlistsep attributes - layerlistsep ignored.\n", *tok);
1157         gvc->layerListDelims = "";
1158     }
1159 
1160     ntok = 0;
1161     sz = 0;
1162     gvc->layers = strdup(p);
1163 
1164     for (tok = strtok(gvc->layers, gvc->layerDelims); tok;
1165          tok = strtok(NULL, gvc->layerDelims)) {
1166         ntok++;
1167         if (ntok > sz) {
1168             sz += SMALLBUF;
1169             gvc->layerIDs = ALLOC(sz, gvc->layerIDs, char *);
1170         }
1171         gvc->layerIDs[ntok] = tok;
1172     }
1173     if (ntok) {
1174         gvc->layerIDs = RALLOC(ntok + 2, gvc->layerIDs, char *);        /* shrink to minimum size */
1175         gvc->layerIDs[0] = NULL;
1176         gvc->layerIDs[ntok + 1] = NULL;
1177     }
1178 
1179     return ntok;
1180 }
1181 
1182 /* chkOrder:
1183  * Determine order of output.
1184  * Output usually in breadth first graph walk order
1185  */
chkOrder(graph_t * g)1186 static int chkOrder(graph_t * g)
1187 {
1188     char *p = agget(g, "outputorder");
1189     if (p) {
1190         char c = *p;
1191         if ((c == 'n') && !strcmp(p + 1, "odesfirst"))
1192             return EMIT_SORTED;
1193         if ((c == 'e') && !strcmp(p + 1, "dgesfirst"))
1194             return EMIT_EDGE_SORTED;
1195     }
1196     return 0;
1197 }
1198 
init_layering(GVC_t * gvc,graph_t * g)1199 static void init_layering(GVC_t * gvc, graph_t * g)
1200 {
1201     char *str;
1202 
1203     /* free layer strings and pointers from previous graph */
1204     if (gvc->layers) {
1205 	free(gvc->layers);
1206 	gvc->layers = NULL;
1207     }
1208     if (gvc->layerIDs) {
1209 	free(gvc->layerIDs);
1210 	gvc->layerIDs = NULL;
1211     }
1212     if (gvc->layerlist) {
1213 	free(gvc->layerlist);
1214 	gvc->layerlist = NULL;
1215     }
1216     if ((str = agget(g, "layers")) != 0) {
1217 	gvc->numLayers = parse_layers(gvc, g, str);
1218  	if (((str = agget(g, "layerselect")) != 0) && *str) {
1219 	    gvc->layerlist = parse_layerselect(gvc, g, str);
1220 	}
1221     } else {
1222 	gvc->layerIDs = NULL;
1223 	gvc->numLayers = 1;
1224     }
1225 }
1226 
1227 /* numPhysicalLayers:
1228  * Return number of physical layers to be emitted.
1229  */
numPhysicalLayers(GVJ_t * job)1230 static int numPhysicalLayers (GVJ_t *job)
1231 {
1232     if (job->gvc->layerlist) {
1233 	return job->gvc->layerlist[0];
1234     }
1235     else
1236 	return job->numLayers;
1237 
1238 }
1239 
firstlayer(GVJ_t * job,int ** listp)1240 static void firstlayer(GVJ_t *job, int** listp)
1241 {
1242     job->numLayers = job->gvc->numLayers;
1243     if (job->gvc->layerlist) {
1244 	int *list = job->gvc->layerlist;
1245 	int cnt = *list++;
1246 	if ((cnt > 1) && (! (job->flags & GVDEVICE_DOES_LAYERS))) {
1247 	    agerr(AGWARN, "layers not supported in %s output\n",
1248 		job->output_langname);
1249 	    list[1] = job->numLayers + 1; /* only one layer printed */
1250 	}
1251 	job->layerNum = *list++;
1252 	*listp = list;
1253     }
1254     else {
1255 	if ((job->numLayers > 1)
1256 		&& (! (job->flags & GVDEVICE_DOES_LAYERS))) {
1257 	    agerr(AGWARN, "layers not supported in %s output\n",
1258 		job->output_langname);
1259 	    job->numLayers = 1;
1260 	}
1261 	job->layerNum = 1;
1262 	*listp = NULL;
1263     }
1264 }
1265 
validlayer(GVJ_t * job)1266 static boolean validlayer(GVJ_t *job)
1267 {
1268     return (job->layerNum <= job->numLayers);
1269 }
1270 
nextlayer(GVJ_t * job,int ** listp)1271 static void nextlayer(GVJ_t *job, int** listp)
1272 {
1273     int *list = *listp;
1274     if (list) {
1275 	job->layerNum = *list++;
1276 	*listp = list;
1277     }
1278     else
1279 	job->layerNum++;
1280 }
1281 
pagecode(GVJ_t * job,char c)1282 static point pagecode(GVJ_t *job, char c)
1283 {
1284     point rv;
1285     rv.x = rv.y = 0;
1286     switch (c) {
1287     case 'T':
1288 	job->pagesArrayFirst.y = job->pagesArraySize.y - 1;
1289 	rv.y = -1;
1290 	break;
1291     case 'B':
1292 	rv.y = 1;
1293 	break;
1294     case 'L':
1295 	rv.x = 1;
1296 	break;
1297     case 'R':
1298 	job->pagesArrayFirst.x = job->pagesArraySize.x - 1;
1299 	rv.x = -1;
1300 	break;
1301     }
1302     return rv;
1303 }
1304 
init_job_pagination(GVJ_t * job,graph_t * g)1305 static void init_job_pagination(GVJ_t * job, graph_t *g)
1306 {
1307     GVC_t *gvc = job->gvc;
1308     pointf pageSize;	/* page size for the graph - points*/
1309     pointf imageSize;	/* image size on one page of the graph - points */
1310     pointf margin;	/* margin for a page of the graph - points */
1311     pointf centering = {0.0, 0.0}; /* centering offset - points */
1312 
1313     /* unpaginated image size - in points - in graph orientation */
1314     imageSize = job->view;
1315 
1316     /* rotate imageSize to page orientation */
1317     if (job->rotation)
1318 	imageSize = exch_xyf(imageSize);
1319 
1320     /* margin - in points - in page orientation */
1321     margin = job->margin;
1322 
1323     /* determine pagination */
1324     if (gvc->graph_sets_pageSize && (job->flags & GVDEVICE_DOES_PAGES)) {
1325 	/* page was set by user */
1326 
1327         /* determine size of page for image */
1328 	pageSize.x = gvc->pageSize.x - 2 * margin.x;
1329 	pageSize.y = gvc->pageSize.y - 2 * margin.y;
1330 
1331 	if (pageSize.x < EPSILON)
1332 	    job->pagesArraySize.x = 1;
1333 	else {
1334 	    job->pagesArraySize.x = (int)(imageSize.x / pageSize.x);
1335 	    if ((imageSize.x - (job->pagesArraySize.x * pageSize.x)) > EPSILON)
1336 		job->pagesArraySize.x++;
1337 	}
1338 	if (pageSize.y < EPSILON)
1339 	    job->pagesArraySize.y = 1;
1340 	else {
1341 	    job->pagesArraySize.y = (int)(imageSize.y / pageSize.y);
1342 	    if ((imageSize.y - (job->pagesArraySize.y * pageSize.y)) > EPSILON)
1343 		job->pagesArraySize.y++;
1344 	}
1345 	job->numPages = job->pagesArraySize.x * job->pagesArraySize.y;
1346 
1347 	/* find the drawable size in points */
1348 	imageSize.x = MIN(imageSize.x, pageSize.x);
1349 	imageSize.y = MIN(imageSize.y, pageSize.y);
1350     } else {
1351 	/* page not set by user, use default from renderer */
1352 	if (job->render.features) {
1353 	    pageSize.x = job->device.features->default_pagesize.x - 2*margin.x;
1354 	    if (pageSize.x < 0.)
1355 		pageSize.x = 0.;
1356 	    pageSize.y = job->device.features->default_pagesize.y - 2*margin.y;
1357 	    if (pageSize.y < 0.)
1358 		pageSize.y = 0.;
1359 	}
1360 	else
1361 	    pageSize.x = pageSize.y = 0.;
1362 	job->pagesArraySize.x = job->pagesArraySize.y = job->numPages = 1;
1363 
1364         if (pageSize.x < imageSize.x)
1365 	    pageSize.x = imageSize.x;
1366         if (pageSize.y < imageSize.y)
1367 	    pageSize.y = imageSize.y;
1368     }
1369 
1370     /* initial window size */
1371 //fprintf(stderr,"page=%g,%g dpi=%g,%g zoom=%g\n", pageSize.x, pageSize.y, job->dpi.x, job->dpi.y, job->zoom);
1372     job->width = ROUND((pageSize.x + 2*margin.x) * job->dpi.x / POINTS_PER_INCH);
1373     job->height = ROUND((pageSize.y + 2*margin.y) * job->dpi.y / POINTS_PER_INCH);
1374 
1375     /* set up pagedir */
1376     job->pagesArrayMajor.x = job->pagesArrayMajor.y
1377 		= job->pagesArrayMinor.x = job->pagesArrayMinor.y = 0;
1378     job->pagesArrayFirst.x = job->pagesArrayFirst.y = 0;
1379     job->pagesArrayMajor = pagecode(job, gvc->pagedir[0]);
1380     job->pagesArrayMinor = pagecode(job, gvc->pagedir[1]);
1381     if ((abs(job->pagesArrayMajor.x + job->pagesArrayMinor.x) != 1)
1382      || (abs(job->pagesArrayMajor.y + job->pagesArrayMinor.y) != 1)) {
1383 	job->pagesArrayMajor = pagecode(job, 'B');
1384 	job->pagesArrayMinor = pagecode(job, 'L');
1385 	agerr(AGWARN, "pagedir=%s ignored\n", gvc->pagedir);
1386     }
1387 
1388     /* determine page box including centering */
1389     if (GD_drawing(g)->centered) {
1390 	if (pageSize.x > imageSize.x)
1391 	    centering.x = (pageSize.x - imageSize.x) / 2;
1392 	if (pageSize.y > imageSize.y)
1393 	    centering.y = (pageSize.y - imageSize.y) / 2;
1394     }
1395 
1396     /* rotate back into graph orientation */
1397     if (job->rotation) {
1398 	imageSize = exch_xyf(imageSize);
1399 	pageSize = exch_xyf(pageSize);
1400 	margin = exch_xyf(margin);
1401 	centering = exch_xyf(centering);
1402     }
1403 
1404     /* canvas area, centered if necessary */
1405     job->canvasBox.LL.x = margin.x + centering.x;
1406     job->canvasBox.LL.y = margin.y + centering.y;
1407     job->canvasBox.UR.x = margin.x + centering.x + imageSize.x;
1408     job->canvasBox.UR.y = margin.y + centering.y + imageSize.y;
1409 
1410     /* size of one page in graph units */
1411     job->pageSize.x = imageSize.x / job->zoom;
1412     job->pageSize.y = imageSize.y / job->zoom;
1413 
1414     /* pageBoundingBox in device units and page orientation */
1415     job->pageBoundingBox.LL.x = ROUND(job->canvasBox.LL.x * job->dpi.x / POINTS_PER_INCH);
1416     job->pageBoundingBox.LL.y = ROUND(job->canvasBox.LL.y * job->dpi.y / POINTS_PER_INCH);
1417     job->pageBoundingBox.UR.x = ROUND(job->canvasBox.UR.x * job->dpi.x / POINTS_PER_INCH);
1418     job->pageBoundingBox.UR.y = ROUND(job->canvasBox.UR.y * job->dpi.y / POINTS_PER_INCH);
1419     if (job->rotation) {
1420         job->pageBoundingBox.LL = exch_xy(job->pageBoundingBox.LL);
1421         job->pageBoundingBox.UR = exch_xy(job->pageBoundingBox.UR);
1422         job->canvasBox.LL = exch_xyf(job->canvasBox.LL);
1423         job->canvasBox.UR = exch_xyf(job->canvasBox.UR);
1424     }
1425 }
1426 
firstpage(GVJ_t * job)1427 static void firstpage(GVJ_t *job)
1428 {
1429     job->pagesArrayElem = job->pagesArrayFirst;
1430 }
1431 
validpage(GVJ_t * job)1432 static boolean validpage(GVJ_t *job)
1433 {
1434     return ((job->pagesArrayElem.x >= 0)
1435 	 && (job->pagesArrayElem.x < job->pagesArraySize.x)
1436 	 && (job->pagesArrayElem.y >= 0)
1437 	 && (job->pagesArrayElem.y < job->pagesArraySize.y));
1438 }
1439 
nextpage(GVJ_t * job)1440 static void nextpage(GVJ_t *job)
1441 {
1442     job->pagesArrayElem = add_point(job->pagesArrayElem, job->pagesArrayMinor);
1443     if (validpage(job) == FALSE) {
1444 	if (job->pagesArrayMajor.y)
1445 	    job->pagesArrayElem.x = job->pagesArrayFirst.x;
1446 	else
1447 	    job->pagesArrayElem.y = job->pagesArrayFirst.y;
1448 	job->pagesArrayElem = add_point(job->pagesArrayElem, job->pagesArrayMajor);
1449     }
1450 }
1451 
write_edge_test(Agraph_t * g,Agedge_t * e)1452 static boolean write_edge_test(Agraph_t * g, Agedge_t * e)
1453 {
1454     Agraph_t *sg;
1455     int c;
1456 
1457     for (c = 1; c <= GD_n_cluster(g); c++) {
1458 	sg = GD_clust(g)[c];
1459 	if (agcontains(sg, e))
1460 	    return FALSE;
1461     }
1462     return TRUE;
1463 }
1464 
write_node_test(Agraph_t * g,Agnode_t * n)1465 static boolean write_node_test(Agraph_t * g, Agnode_t * n)
1466 {
1467     Agraph_t *sg;
1468     int c;
1469 
1470     for (c = 1; c <= GD_n_cluster(g); c++) {
1471 	sg = GD_clust(g)[c];
1472 	if (agcontains(sg, n))
1473 	    return FALSE;
1474     }
1475     return TRUE;
1476 }
1477 
1478 #define INITPTS 1000
1479 
1480 static pointf*
copyPts(pointf * pts,int * ptsize,xdot_point * inpts,int numpts)1481 copyPts (pointf* pts, int* ptsize, xdot_point* inpts, int numpts)
1482 {
1483     int i, sz = *ptsize;
1484 
1485     if (numpts > sz) {
1486 	sz = MAX(2*sz, numpts);
1487 	pts = RALLOC(sz, pts, pointf);
1488 	*ptsize = sz;
1489     }
1490 
1491     for (i = 0; i < numpts; i++) {
1492 	pts[i].x = inpts[i].x;
1493 	pts[i].y = inpts[i].y;
1494     }
1495 
1496     return pts;
1497 }
1498 
emit_xdot(GVJ_t * job,xdot * xd)1499 static void emit_xdot (GVJ_t * job, xdot* xd)
1500 {
1501     int image_warn = 1;
1502     int ptsize = INITPTS;
1503     pointf* pts = N_GNEW(INITPTS, pointf);
1504     exdot_op* op;
1505     int i, angle;
1506     char** styles = 0;
1507     int filled = FILL;
1508 
1509     op = (exdot_op*)(xd->ops);
1510     for (i = 0; i < xd->cnt; i++) {
1511 	switch (op->op.kind) {
1512 	case xd_filled_ellipse :
1513 	case xd_unfilled_ellipse :
1514     	    if (boxf_overlap(op->bb, job->clip)) {
1515 		pts[0].x = op->op.u.ellipse.x - op->op.u.ellipse.w;
1516 		pts[0].y = op->op.u.ellipse.y - op->op.u.ellipse.h;
1517 		pts[1].x = op->op.u.ellipse.x + op->op.u.ellipse.w;
1518 		pts[1].y = op->op.u.ellipse.y + op->op.u.ellipse.h;
1519 		gvrender_ellipse(job, pts, 2, (op->op.kind == xd_filled_ellipse?filled:0));
1520 	    }
1521 	    break;
1522 	case xd_filled_polygon :
1523 	case xd_unfilled_polygon :
1524     	    if (boxf_overlap(op->bb, job->clip)) {
1525 		pts = copyPts (pts, &ptsize, op->op.u.polygon.pts, op->op.u.polygon.cnt);
1526 		gvrender_polygon(job, pts, op->op.u.polygon.cnt, (op->op.kind == xd_filled_polygon?filled:0));
1527 	    }
1528 	    break;
1529 	case xd_filled_bezier :
1530 	case xd_unfilled_bezier :
1531     	    if (boxf_overlap(op->bb, job->clip)) {
1532 		pts = copyPts (pts, &ptsize, op->op.u.bezier.pts, op->op.u.bezier.cnt);
1533 		gvrender_beziercurve(job, pts, op->op.u.bezier.cnt, 0, 0, (op->op.kind == xd_filled_bezier?filled:0));
1534 	    }
1535 	    break;
1536 	case xd_polyline :
1537     	    if (boxf_overlap(op->bb, job->clip)) {
1538 		pts = copyPts (pts, &ptsize, op->op.u.polyline.pts, op->op.u.polyline.cnt);
1539 		gvrender_polyline(job, pts, op->op.u.polyline.cnt);
1540 	    }
1541 	    break;
1542 	case xd_text :
1543     	    if (boxf_overlap(op->bb, job->clip)) {
1544 		pts[0].x = op->op.u.text.x;
1545 		pts[0].y = op->op.u.text.y;
1546 		gvrender_textspan(job, pts[0], op->span);
1547 	    }
1548 	    break;
1549 	case xd_fill_color :
1550             gvrender_set_fillcolor(job, op->op.u.color);
1551 	    filled = FILL;
1552 	    break;
1553 	case xd_pen_color :
1554             gvrender_set_pencolor(job, op->op.u.color);
1555 	    filled = FILL;
1556 	    break;
1557 	case xd_grad_fill_color :
1558 	    {
1559 		char* clr0;
1560 		char* clr1;
1561 		float frac;
1562 		if (op->op.u.grad_color.type == xd_radial) {
1563 		    xdot_radial_grad* p = &op->op.u.grad_color.u.ring;
1564 		    clr0 = p->stops[0].color;
1565 		    clr1 = p->stops[1].color;
1566 		    frac = p->stops[1].frac;
1567 		    if ((p->x1 == p->x0) && (p->y1 == p->y0))
1568 			angle = 0;
1569 		    else
1570 			angle = (int)(180.0*acos((p->x0 - p->x1)/p->r0)/M_PI);
1571         	    gvrender_set_fillcolor(job, clr0);
1572 		    gvrender_set_gradient_vals(job, clr1, angle, frac);
1573 		    filled = RGRADIENT;
1574 		}
1575 		else {
1576 		    xdot_linear_grad* p = &op->op.u.grad_color.u.ling;
1577 		    clr0 = p->stops[0].color;
1578 		    clr1 = p->stops[1].color;
1579 		    frac = p->stops[1].frac;
1580 		    angle = (int)(180.0*atan2(p->y1-p->y0,p->x1-p->x0)/M_PI);
1581         	    gvrender_set_fillcolor(job, clr0);
1582 		    gvrender_set_gradient_vals(job, clr1, angle, frac);
1583 		    filled = GRADIENT;
1584 		}
1585 	    }
1586 	    break;
1587 	case xd_grad_pen_color :
1588 	    agerr (AGWARN, "gradient pen colors not yet supported.\n");
1589 	    break;
1590 	case xd_font :
1591 	    /* fontsize and fontname already encoded via xdotBB */
1592 	    break;
1593 	case xd_style :
1594 	    styles = parse_style (op->op.u.style);
1595             gvrender_set_style (job, styles);
1596 	    break;
1597 	case xd_fontchar :
1598 	    /* font characteristics already encoded via xdotBB */
1599 	    break;
1600 	case xd_image :
1601 	    if (image_warn) {
1602 	        agerr(AGWARN, "Images unsupported in \"background\" attribute\n");
1603 	        image_warn = 0;
1604 	    }
1605 	    break;
1606 	}
1607 	op++;
1608     }
1609     if (styles)
1610 	gvrender_set_style(job, job->gvc->defaultlinestyle);
1611     free (pts);
1612 }
1613 
emit_background(GVJ_t * job,graph_t * g)1614 static void emit_background(GVJ_t * job, graph_t *g)
1615 {
1616     xdot* xd;
1617     char *str;
1618     int dfltColor;
1619 
1620     /* if no bgcolor specified - first assume default of "white" */
1621     if (! ((str = agget(g, "bgcolor")) && str[0])) {
1622 	str = "white";
1623 	dfltColor = 1;
1624     }
1625     else
1626 	dfltColor = 0;
1627 
1628 
1629     /* if device has no truecolor support, change "transparent" to "white" */
1630     if (! (job->flags & GVDEVICE_DOES_TRUECOLOR) && (streq(str, "transparent"))) {
1631 	str = "white";
1632 	dfltColor = 1;
1633     }
1634 
1635     /* except for "transparent" on truecolor, or default "white" on (assumed) white paper, paint background */
1636     if (!(   ((job->flags & GVDEVICE_DOES_TRUECOLOR) && streq(str, "transparent"))
1637           || ((job->flags & GVRENDER_NO_WHITE_BG) && dfltColor))) {
1638 	char* clrs[2];
1639 	float frac;
1640 
1641 	if ((findStopColor (str, clrs, &frac))) {
1642 	    int filled, istyle = 0;
1643             gvrender_set_fillcolor(job, clrs[0]);
1644             gvrender_set_pencolor(job, "transparent");
1645 	    checkClusterStyle(g, &istyle);
1646 	    if (clrs[1])
1647 		gvrender_set_gradient_vals(job,clrs[1],late_int(g,G_gradientangle,0,0), frac);
1648 	    else
1649 		gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(g,G_gradientangle,0,0), frac);
1650 	    if (istyle & RADIAL)
1651 		filled = RGRADIENT;
1652 	    else
1653 		filled = GRADIENT;
1654 	    gvrender_box(job, job->clip, filled);
1655 	    free (clrs[0]);
1656 	}
1657 	else {
1658             gvrender_set_fillcolor(job, str);
1659     	    gvrender_set_pencolor(job, "transparent");
1660 	    gvrender_box(job, job->clip, FILL);	/* filled */
1661 	}
1662     }
1663 
1664     if ((xd = (xdot*)GD_drawing(g)->xdots))
1665 	emit_xdot (job, xd);
1666 }
1667 
setup_page(GVJ_t * job,graph_t * g)1668 static void setup_page(GVJ_t * job, graph_t * g)
1669 {
1670     point pagesArrayElem = job->pagesArrayElem, pagesArraySize = job->pagesArraySize;
1671 
1672     if (job->rotation) {
1673 	pagesArrayElem = exch_xy(pagesArrayElem);
1674 	pagesArraySize = exch_xy(pagesArraySize);
1675     }
1676 
1677     /* establish current box in graph units */
1678     job->pageBox.LL.x = pagesArrayElem.x * job->pageSize.x - job->pad.x;
1679     job->pageBox.LL.y = pagesArrayElem.y * job->pageSize.y - job->pad.y;
1680     job->pageBox.UR.x = job->pageBox.LL.x + job->pageSize.x;
1681     job->pageBox.UR.y = job->pageBox.LL.y + job->pageSize.y;
1682 
1683     /* maximum boundingBox in device units and page orientation */
1684     if (job->common->viewNum == 0)
1685         job->boundingBox = job->pageBoundingBox;
1686     else
1687         EXPANDBB(job->boundingBox, job->pageBoundingBox);
1688 
1689     if (job->flags & GVDEVICE_EVENTS) {
1690         job->clip.LL.x = job->focus.x - job->view.x / 2.;
1691         job->clip.LL.y = job->focus.y - job->view.y / 2.;
1692         job->clip.UR.x = job->focus.x + job->view.x / 2.;
1693         job->clip.UR.y = job->focus.y + job->view.y / 2.;
1694     }
1695     else {
1696         job->clip.LL.x = job->focus.x + job->pageSize.x * (pagesArrayElem.x - pagesArraySize.x / 2.);
1697         job->clip.LL.y = job->focus.y + job->pageSize.y * (pagesArrayElem.y - pagesArraySize.y / 2.);
1698         job->clip.UR.x = job->clip.LL.x + job->pageSize.x;
1699         job->clip.UR.y = job->clip.LL.y + job->pageSize.y;
1700     }
1701 
1702     /* CAUTION - job->translation was difficult to get right. */
1703     /* Test with and without assymmetric margins, e.g: -Gmargin="1,0" */
1704     if (job->rotation) {
1705 	job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
1706         if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
1707             job->translation.x = - job->clip.UR.x - job->canvasBox.LL.x / job->zoom;
1708         else
1709             job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
1710     }
1711     else {
1712 	/* pre unscale margins to keep them constant under scaling */
1713         job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
1714         if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
1715             job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
1716         else
1717             job->translation.y = - job->clip.LL.y + job->canvasBox.LL.y / job->zoom;
1718     }
1719 
1720 #if 0
1721 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",
1722 	job->width, job->height,
1723 	job->dpi.x, job->dpi.y,
1724 	job->pad.x, job->pad.y,
1725 	job->focus.x, job->focus.y,
1726 	job->view.x, job->view.y,
1727 	job->zoom,
1728 	job->pageBox.LL.x, job->pageBox.LL.y, job->pageBox.UR.x, job->pageBox.UR.y,
1729 	job->pagesArraySize.x, job->pagesArraySize.y,
1730 	job->pageSize.x, job->pageSize.y,
1731 	job->canvasBox.LL.x, job->canvasBox.LL.y, job->canvasBox.UR.x, job->canvasBox.UR.y,
1732 	job->pageOffset.x, job->pageOffset.y,
1733 	job->translation.x, job->translation.y,
1734 	job->clip.LL.x, job->clip.LL.y, job->clip.UR.x, job->clip.UR.y,
1735 	job->margin.x, job->margin.y);
1736 #endif
1737 }
1738 
node_in_layer(GVJ_t * job,graph_t * g,node_t * n)1739 static boolean node_in_layer(GVJ_t *job, graph_t * g, node_t * n)
1740 {
1741     char *pn, *pe;
1742     edge_t *e;
1743 
1744     if (job->numLayers <= 1)
1745 	return TRUE;
1746     pn = late_string(n, N_layer, "");
1747     if (selectedlayer(job, pn))
1748 	return TRUE;
1749     if (pn[0])
1750 	return FALSE;		/* Only check edges if pn = "" */
1751     if ((e = agfstedge(g, n)) == NULL)
1752 	return TRUE;
1753     for (e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
1754 	pe = late_string(e, E_layer, "");
1755 	if ((pe[0] == '\0') || selectedlayer(job, pe))
1756 	    return TRUE;
1757     }
1758     return FALSE;
1759 }
1760 
edge_in_layer(GVJ_t * job,graph_t * g,edge_t * e)1761 static boolean edge_in_layer(GVJ_t *job, graph_t * g, edge_t * e)
1762 {
1763     char *pe, *pn;
1764     int cnt;
1765 
1766     if (job->numLayers <= 1)
1767 	return TRUE;
1768     pe = late_string(e, E_layer, "");
1769     if (selectedlayer(job, pe))
1770 	return TRUE;
1771     if (pe[0])
1772 	return FALSE;
1773     for (cnt = 0; cnt < 2; cnt++) {
1774 	pn = late_string(cnt < 1 ? agtail(e) : aghead(e), N_layer, "");
1775 	if ((pn[0] == '\0') || selectedlayer(job, pn))
1776 	    return TRUE;
1777     }
1778     return FALSE;
1779 }
1780 
clust_in_layer(GVJ_t * job,graph_t * sg)1781 static boolean clust_in_layer(GVJ_t *job, graph_t * sg)
1782 {
1783     char *pg;
1784     node_t *n;
1785 
1786     if (job->numLayers <= 1)
1787 	return TRUE;
1788     pg = late_string(sg, agattr(sg, AGRAPH, "layer", 0), "");
1789     if (selectedlayer(job, pg))
1790 	return TRUE;
1791     if (pg[0])
1792 	return FALSE;
1793     for (n = agfstnode(sg); n; n = agnxtnode(sg, n))
1794 	if (node_in_layer(job, sg, n))
1795 	    return TRUE;
1796     return FALSE;
1797 }
1798 
node_in_box(node_t * n,boxf b)1799 static boolean node_in_box(node_t *n, boxf b)
1800 {
1801     return boxf_overlap(ND_bb(n), b);
1802 }
1803 
emit_begin_node(GVJ_t * job,node_t * n)1804 static void emit_begin_node(GVJ_t * job, node_t * n)
1805 {
1806     obj_state_t *obj;
1807     int flags = job->flags;
1808     int sides, peripheries, i, j, filled = 0, rect = 0, shape, nump = 0;
1809     polygon_t *poly = NULL;
1810     pointf *vertices, *p = NULL;
1811     pointf coord;
1812     char *s;
1813 
1814     obj = push_obj_state(job);
1815     obj->type = NODE_OBJTYPE;
1816     obj->u.n = n;
1817     obj->emit_state = EMIT_NDRAW;
1818 
1819     if (flags & GVRENDER_DOES_Z) {
1820         /* obj->z = late_double(n, N_z, 0.0, -MAXFLOAT); */
1821 	if (GD_odim(agraphof(n)) >=3)
1822             obj->z = POINTS(ND_pos(n)[2]);
1823 	else
1824             obj->z = 0.0;
1825     }
1826     initObjMapData (job, ND_label(n), n);
1827     if ((flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS))
1828            && (obj->url || obj->explicit_tooltip)) {
1829 
1830         /* checking shape of node */
1831         shape = shapeOf(n);
1832         /* node coordinate */
1833         coord = ND_coord(n);
1834         /* checking if filled style has been set for node */
1835         filled = ifFilled(n);
1836 
1837         if (shape == SH_POLY || shape == SH_POINT) {
1838             poly = (polygon_t *) ND_shape_info(n);
1839 
1840             /* checking if polygon is regular rectangle */
1841             if (isRect(poly) && (poly->peripheries || filled))
1842                 rect = 1;
1843         }
1844 
1845         /* When node has polygon shape and requested output supports polygons
1846          * we use a polygon to map the clickable region that is a:
1847          * circle, ellipse, polygon with n side, or point.
1848          * For regular rectangular shape we have use node's bounding box to map clickable region
1849          */
1850         if (poly && !rect && (flags & GVRENDER_DOES_MAP_POLYGON)) {
1851 
1852             if (poly->sides < 3)
1853                 sides = 1;
1854             else
1855                 sides = poly->sides;
1856 
1857             if (poly->peripheries < 2)
1858                 peripheries = 1;
1859             else
1860                 peripheries = poly->peripheries;
1861 
1862             vertices = poly->vertices;
1863 
1864             if ((s = agget(n, "samplepoints")))
1865                 nump = atoi(s);
1866             /* We want at least 4 points. For server-side maps, at most 100
1867              * points are allowed. To simplify things to fit with the 120 points
1868              * used for skewed ellipses, we set the bound at 60.
1869              */
1870             if ((nump < 4) || (nump > 60))
1871                 nump = DFLT_SAMPLE;
1872             /* use bounding box of text label or node image for mapping
1873              * when polygon has no peripheries and node is not filled
1874              */
1875             if (poly->peripheries == 0 && !filled) {
1876                 obj->url_map_shape = MAP_RECTANGLE;
1877                 nump = 2;
1878                 p = N_NEW(nump, pointf);
1879                 P2RECT(coord, p, ND_lw(n), ND_ht(n) / 2.0 );
1880             }
1881             /* circle or ellipse */
1882             else if (poly->sides < 3 && poly->skew == 0.0 && poly->distortion == 0.0) {
1883                 if (poly->regular) {
1884                     obj->url_map_shape = MAP_CIRCLE;
1885                     nump = 2;              /* center of circle and top right corner of bb */
1886                     p = N_NEW(nump, pointf);
1887                     p[0].x = coord.x;
1888                     p[0].y = coord.y;
1889 		    /* even vertices contain LL corner of bb */
1890 		    /* odd vertices contain UR corner of bb */
1891                     p[1].x = coord.x + vertices[2*peripheries - 1].x;
1892                     p[1].y = coord.y + vertices[2*peripheries - 1].y;
1893                 }
1894                 else { /* ellipse is treated as polygon */
1895                     obj->url_map_shape= MAP_POLYGON;
1896                     p = pEllipse((double)(vertices[2*peripheries - 1].x),
1897                                  (double)(vertices[2*peripheries - 1].y), nump);
1898                     for (i = 0; i < nump; i++) {
1899                         p[i].x += coord.x;
1900                         p[i].y += coord.y;
1901                     }
1902 		}
1903             }
1904             /* all other polygonal shape */
1905             else {
1906                 int offset = (peripheries - 1)*(poly->sides);
1907                 obj->url_map_shape = MAP_POLYGON;
1908                 /* distorted or skewed ellipses and circles are polygons with 120
1909                  * sides. For mapping we convert them into polygon with sample sides
1910                  */
1911                 if (poly->sides >= nump) {
1912                     int delta = poly->sides / nump;
1913                     p = N_NEW(nump, pointf);
1914                     for (i = 0, j = 0; j < nump; i += delta, j++) {
1915                         p[j].x = coord.x + vertices[i + offset].x;
1916                         p[j].y = coord.y + vertices[i + offset].y;
1917                     }
1918                 } else {
1919                     nump = sides;
1920                     p = N_NEW(nump, pointf);
1921                     for (i = 0; i < nump; i++) {
1922                         p[i].x = coord.x + vertices[i + offset].x;
1923                         p[i].y = coord.y + vertices[i + offset].y;
1924                     }
1925                 }
1926             }
1927         }
1928         else {
1929             /* we have to use the node's bounding box to map clickable region
1930              * when requested output format is not capable of polygons.
1931              */
1932             obj->url_map_shape = MAP_RECTANGLE;
1933             nump = 2;
1934             p = N_NEW(nump, pointf);
1935             p[0].x = coord.x - ND_lw(n);
1936             p[0].y = coord.y - (ND_ht(n) / 2);
1937             p[1].x = coord.x + ND_rw(n);
1938             p[1].y = coord.y + (ND_ht(n) / 2);
1939         }
1940         if (! (flags & GVRENDER_DOES_TRANSFORM))
1941             gvrender_ptf_A(job, p, p, nump);
1942         obj->url_map_p = p;
1943         obj->url_map_n = nump;
1944     }
1945 
1946     setColorScheme (agget (n, "colorscheme"));
1947     gvrender_begin_node(job, n);
1948 }
1949 
emit_end_node(GVJ_t * job)1950 static void emit_end_node(GVJ_t * job)
1951 {
1952     gvrender_end_node(job);
1953     pop_obj_state(job);
1954 }
1955 
1956 /* emit_node:
1957  */
emit_node(GVJ_t * job,node_t * n)1958 static void emit_node(GVJ_t * job, node_t * n)
1959 {
1960     GVC_t *gvc = job->gvc;
1961     char *s;
1962     char *style;
1963     char **styles = 0;
1964     char **sp;
1965     char *p;
1966 
1967     if (ND_shape(n) 				     /* node has a shape */
1968 	    && node_in_layer(job, agraphof(n), n)    /* and is in layer */
1969 	    && node_in_box(n, job->clip)             /* and is in page/view */
1970 	    && (ND_state(n) != gvc->common.viewNum)) /* and not already drawn */
1971     {
1972 	ND_state(n) = gvc->common.viewNum; 	     /* mark node as drawn */
1973 
1974         gvrender_comment(job, agnameof(n));
1975 	s = late_string(n, N_comment, "");
1976 	if (s[0])
1977 	    gvrender_comment(job, s);
1978 
1979 	style = late_string(n, N_style, "");
1980 	if (style[0]) {
1981 	    styles = parse_style(style);
1982 	    sp = styles;
1983 	    while ((p = *sp++)) {
1984 		if (streq(p, "invis")) return;
1985 	    }
1986 	}
1987 
1988 	emit_begin_node(job, n);
1989 	ND_shape(n)->fns->codefn(job, n);
1990 	if (ND_xlabel(n) && ND_xlabel(n)->set)
1991 	    emit_label(job, EMIT_NLABEL, ND_xlabel(n));
1992 	emit_end_node(job);
1993     }
1994 }
1995 
1996 /* calculate an offset vector, length d, perpendicular to line p,q */
computeoffset_p(pointf p,pointf q,double d)1997 static pointf computeoffset_p(pointf p, pointf q, double d)
1998 {
1999     pointf res;
2000     double x = p.x - q.x, y = p.y - q.y;
2001 
2002     /* keep d finite as line length approaches 0 */
2003     d /= sqrt(x * x + y * y + EPSILON);
2004     res.x = y * d;
2005     res.y = -x * d;
2006     return res;
2007 }
2008 
2009 /* calculate offset vector, length d, perpendicular to spline p,q,r,s at q&r */
computeoffset_qr(pointf p,pointf q,pointf r,pointf s,double d)2010 static pointf computeoffset_qr(pointf p, pointf q, pointf r, pointf s,
2011 			       double d)
2012 {
2013     pointf res;
2014     double len;
2015     double x = q.x - r.x, y = q.y - r.y;
2016 
2017     len = sqrt(x * x + y * y);
2018     if (len < EPSILON) {
2019 	/* control points are on top of each other
2020 	   use slope between endpoints instead */
2021 	x = p.x - s.x, y = p.y - s.y;
2022 	/* keep d finite as line length approaches 0 */
2023 	len = sqrt(x * x + y * y + EPSILON);
2024     }
2025     d /= len;
2026     res.x = y * d;
2027     res.y = -x * d;
2028     return res;
2029 }
2030 
emit_attachment(GVJ_t * job,textlabel_t * lp,splines * spl)2031 static void emit_attachment(GVJ_t * job, textlabel_t * lp, splines * spl)
2032 {
2033     pointf sz, AF[3];
2034     unsigned char *s;
2035 
2036     for (s = (unsigned char *) (lp->text); *s; s++) {
2037 	if (isspace(*s) == FALSE)
2038 	    break;
2039     }
2040     if (*s == 0)
2041 	return;
2042 
2043     sz = lp->dimen;
2044     AF[0] = pointfof(lp->pos.x + sz.x / 2., lp->pos.y - sz.y / 2.);
2045     AF[1] = pointfof(AF[0].x - sz.x, AF[0].y);
2046     AF[2] = dotneato_closest(spl, lp->pos);
2047     /* Don't use edge style to draw attachment */
2048     gvrender_set_style(job, job->gvc->defaultlinestyle);
2049     /* Use font color to draw attachment
2050        - need something unambiguous in case of multicolored parallel edges
2051        - defaults to black for html-like labels
2052      */
2053     gvrender_set_pencolor(job, lp->fontcolor);
2054     gvrender_polyline(job, AF, 3);
2055 }
2056 
2057 /* edges colors can be mutiple colors separated by ":"
2058  * so we commpute a default pencolor with the same number of colors. */
default_pencolor(char * pencolor,char * deflt)2059 static char* default_pencolor(char *pencolor, char *deflt)
2060 {
2061     static char *buf;
2062     static int bufsz;
2063     char *p;
2064     int len, ncol;
2065 
2066     ncol = 1;
2067     for (p = pencolor; *p; p++) {
2068 	if (*p == ':')
2069 	    ncol++;
2070     }
2071     len = ncol * (strlen(deflt) + 1);
2072     if (bufsz < len) {
2073 	bufsz = len + 10;
2074 	buf = realloc(buf, bufsz);
2075     }
2076     strcpy(buf, deflt);
2077     while(--ncol) {
2078 	strcat(buf, ":");
2079 	strcat(buf, deflt);
2080     }
2081     return buf;
2082 }
2083 
2084 /* approxLen:
2085  */
approxLen(pointf * pts)2086 static double approxLen (pointf* pts)
2087 {
2088     double d = DIST(pts[0],pts[1]);
2089     d += DIST(pts[1],pts[2]);
2090     d += DIST(pts[2],pts[3]);
2091     return d;
2092 }
2093 
2094 /* splitBSpline:
2095  * Given B-spline bz and 0 < t < 1, split bz so that left corresponds to
2096  * the fraction t of the arc length. The new parts are store in left and right.
2097  * The caller needs to free the allocated points.
2098  *
2099  * In the current implementation, we find the Bezier that should contain t by
2100  * treating the control points as a polyline.
2101  * We then split that Bezier.
2102  */
splitBSpline(bezier * bz,float t,bezier * left,bezier * right)2103 static void splitBSpline (bezier* bz, float t, bezier* left, bezier* right)
2104 {
2105     int i, j, k, cnt = (bz->size - 1)/3;
2106     double* lens;
2107     double last, len, sum;
2108     pointf* pts;
2109     float r;
2110 
2111     if (cnt == 1) {
2112 	left->size = 4;
2113 	left->list = N_NEW(4, pointf);
2114 	right->size = 4;
2115 	right->list = N_NEW(4, pointf);
2116 	Bezier (bz->list, 3, t, left->list, right->list);
2117 	return;
2118     }
2119 
2120     lens = N_NEW(cnt, double);
2121     sum = 0;
2122     pts = bz->list;
2123     for (i = 0; i < cnt; i++) {
2124 	lens[i] = approxLen (pts);
2125 	sum += lens[i];
2126 	pts += 3;
2127     }
2128     len = t*sum;
2129     sum = 0;
2130     for (i = 0; i < cnt; i++) {
2131 	sum += lens[i];
2132 	if (sum >= len)
2133 	    break;
2134     }
2135 
2136     left->size = 3*(i+1) + 1;
2137     left->list = N_NEW(left->size,pointf);
2138     right->size = 3*(cnt-i) + 1;
2139     right->list = N_NEW(right->size,pointf);
2140     for (j = 0; j < left->size; j++)
2141 	left->list[j] = bz->list[j];
2142     k = j - 4;
2143     for (j = 0; j < right->size; j++)
2144 	right->list[j] = bz->list[k++];
2145 
2146     last = lens[i];
2147     r = (len - (sum - last))/last;
2148     Bezier (bz->list + 3*i, 3, r, left->list + 3*i, right->list);
2149 
2150     free (lens);
2151 }
2152 
2153 /* multicolor:
2154  * Draw an edge as a sequence of colors.
2155  * Not sure how to handle multiple B-splines, so do a naive
2156  * implementation.
2157  * Return non-zero if color spec is incorrect
2158  */
multicolor(GVJ_t * job,edge_t * e,char ** styles,char * colors,int num,double arrowsize,double penwidth)2159 static int multicolor (GVJ_t * job, edge_t * e, char** styles, char* colors, int num, double arrowsize, double penwidth)
2160 {
2161     bezier bz;
2162     bezier bz0, bz_l, bz_r;
2163     int i, rv;
2164     colorsegs_t* segs;
2165     colorseg_t* s;
2166     char* endcolor = NULL;
2167     double left;
2168     int first;  /* first segment with t > 0 */
2169 
2170     rv = parseSegs (colors, num, &segs);
2171     if (rv > 1) {
2172 	Agraph_t* g = agraphof(agtail(e));
2173 	agerr (AGPREV, "in edge %s%s%s\n", agnameof(agtail(e)), (agisdirected(g)?" -> ":" -- "), agnameof(aghead(e)));
2174 
2175 	if (rv == 2)
2176 	    return 1;
2177     }
2178     else if (rv == 1)
2179 	return 1;
2180 
2181 
2182     for (i = 0; i < ED_spl(e)->size; i++) {
2183 	left = 1;
2184 	bz = ED_spl(e)->list[i];
2185 	first = 1;
2186 	for (s = segs->segs; s->color; s++) {
2187 	    if (AEQ0(s->t)) continue;
2188     	    gvrender_set_pencolor(job, s->color);
2189 	    left -= s->t;
2190 	    endcolor = s->color;
2191 	    if (first) {
2192 		first = 0;
2193 		splitBSpline (&bz, s->t, &bz_l, &bz_r);
2194 		gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE);
2195 		free (bz_l.list);
2196 		if (AEQ0(left)) {
2197 		    free (bz_r.list);
2198 		    break;
2199 		}
2200 	    }
2201 	    else if (AEQ0(left)) {
2202 		gvrender_beziercurve(job, bz_r.list, bz_r.size, FALSE, FALSE, FALSE);
2203 		free (bz_r.list);
2204 		break;
2205 	    }
2206 	    else {
2207 		bz0 = bz_r;
2208 		splitBSpline (&bz0, (s->t)/(left+s->t), &bz_l, &bz_r);
2209 		free (bz0.list);
2210 		gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE);
2211 		free (bz_l.list);
2212 	    }
2213 
2214 	}
2215                 /* arrow_gen resets the job style  (How?  FIXME)
2216                  * If we have more splines to do, restore the old one.
2217                  * Use local copy of penwidth to work around reset.
2218                  */
2219 	if (bz.sflag) {
2220     	    gvrender_set_pencolor(job, segs->segs->color);
2221     	    gvrender_set_fillcolor(job, segs->segs->color);
2222 	    arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2223 	}
2224 	if (bz.eflag) {
2225     	    gvrender_set_pencolor(job, endcolor);
2226     	    gvrender_set_fillcolor(job, endcolor);
2227 	    arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2228 	}
2229 	if ((ED_spl(e)->size>1) && (bz.sflag||bz.eflag) && styles)
2230 	    gvrender_set_style(job, styles);
2231     }
2232     freeSegs (segs);
2233     return 0;
2234 }
2235 
free_stroke(stroke_t * sp)2236 static void free_stroke (stroke_t* sp)
2237 {
2238     if (sp) {
2239 	free (sp->vertices);
2240 	free (sp);
2241     }
2242 }
2243 
2244 typedef double (*radfunc_t)(double,double,double);
2245 
forfunc(double curlen,double totallen,double initwid)2246 static double forfunc (double curlen, double totallen, double initwid)
2247 {
2248     return ((1 - (curlen/totallen))*initwid/2.0);
2249 }
2250 
revfunc(double curlen,double totallen,double initwid)2251 static double revfunc (double curlen, double totallen, double initwid)
2252 {
2253     return (((curlen/totallen))*initwid/2.0);
2254 }
2255 
nonefunc(double curlen,double totallen,double initwid)2256 static double nonefunc (double curlen, double totallen, double initwid)
2257 {
2258     return (initwid/2.0);
2259 }
2260 
bothfunc(double curlen,double totallen,double initwid)2261 static double bothfunc (double curlen, double totallen, double initwid)
2262 {
2263     double fr = curlen/totallen;
2264     if (fr <= 0.5) return (fr*initwid);
2265     else return ((1-fr)*initwid);
2266 }
2267 
2268 static radfunc_t
taperfun(edge_t * e)2269 taperfun (edge_t* e)
2270 {
2271     char* attr;
2272     if (E_dir && ((attr = agxget(e, E_dir)))[0]) {
2273 	if (streq(attr, "forward")) return forfunc;
2274 	if (streq(attr, "back")) return revfunc;
2275 	if (streq(attr, "both")) return bothfunc;
2276 	if (streq(attr, "none")) return nonefunc;
2277     }
2278     return (agisdirected(agraphof(aghead(e))) ? forfunc : nonefunc);
2279 }
2280 
emit_edge_graphics(GVJ_t * job,edge_t * e,char ** styles)2281 static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
2282 {
2283     int i, j, cnum, numc = 0, numsemi = 0;
2284     char *color, *pencolor, *fillcolor;
2285     char *headcolor, *tailcolor, *lastcolor;
2286     char *colors = NULL;
2287     bezier bz;
2288     splines offspl, tmpspl;
2289     pointf pf0, pf1, pf2 = { 0, 0 }, pf3, *offlist, *tmplist;
2290     double arrowsize, numc2, penwidth=job->obj->penwidth;
2291     char* p;
2292     boolean tapered = 0;
2293 
2294 #define SEP 2.0
2295 
2296     setColorScheme (agget (e, "colorscheme"));
2297     if (ED_spl(e)) {
2298 	arrowsize = late_double(e, E_arrowsz, 1.0, 0.0);
2299 	color = late_string(e, E_color, "");
2300 
2301 	if (styles) {
2302 	    char** sp = styles;
2303 	    while ((p = *sp++)) {
2304 		if (streq(p, "tapered")) {
2305 		    tapered = 1;
2306 		    break;
2307 		}
2308 	    }
2309 	}
2310 
2311 	/* need to know how many colors separated by ':' */
2312 	for (p = color; *p; p++) {
2313 	    if (*p == ':')
2314 		numc++;
2315 	    else if (*p == ';')
2316 		numsemi++;
2317 	}
2318 
2319 	if (numsemi && numc) {
2320 	    if (multicolor (job, e, styles, color, numc+1, arrowsize, penwidth)) {
2321 		color = DEFAULT_COLOR;
2322 	    }
2323 	    else
2324 		return;
2325 	}
2326 
2327 	fillcolor = pencolor = color;
2328 	if (ED_gui_state(e) & GUI_STATE_ACTIVE) {
2329 	    pencolor = late_nnstring(e, E_activepencolor,
2330 			default_pencolor(pencolor, DEFAULT_ACTIVEPENCOLOR));
2331 	    fillcolor = late_nnstring(e, E_activefillcolor, DEFAULT_ACTIVEFILLCOLOR);
2332 	}
2333 	else if (ED_gui_state(e) & GUI_STATE_SELECTED) {
2334 	    pencolor = late_nnstring(e, E_selectedpencolor,
2335 			default_pencolor(pencolor, DEFAULT_SELECTEDPENCOLOR));
2336 	    fillcolor = late_nnstring(e, E_selectedfillcolor, DEFAULT_SELECTEDFILLCOLOR);
2337 	}
2338 	else if (ED_gui_state(e) & GUI_STATE_DELETED) {
2339 	    pencolor = late_nnstring(e, E_deletedpencolor,
2340 			default_pencolor(pencolor, DEFAULT_DELETEDPENCOLOR));
2341 	    fillcolor = late_nnstring(e, E_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR);
2342 	}
2343 	else if (ED_gui_state(e) & GUI_STATE_VISITED) {
2344 	    pencolor = late_nnstring(e, E_visitedpencolor,
2345 			default_pencolor(pencolor, DEFAULT_VISITEDPENCOLOR));
2346 	    fillcolor = late_nnstring(e, E_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR);
2347 	}
2348 	else
2349 	    fillcolor = late_nnstring(e, E_fillcolor, color);
2350 	if (pencolor != color)
2351     	    gvrender_set_pencolor(job, pencolor);
2352 	if (fillcolor != color)
2353 	    gvrender_set_fillcolor(job, fillcolor);
2354 	color = pencolor;
2355 
2356 	if (tapered) {
2357 	    stroke_t* stp;
2358 	    if (*color == '\0') color = DEFAULT_COLOR;
2359 	    if (*fillcolor == '\0') fillcolor = DEFAULT_COLOR;
2360     	    gvrender_set_pencolor(job, "transparent");
2361 	    gvrender_set_fillcolor(job, color);
2362 	    bz = ED_spl(e)->list[0];
2363 	    stp = taper (&bz, taperfun (e), penwidth, 0, 0);
2364 	    gvrender_polygon(job, stp->vertices, stp->nvertices, TRUE);
2365 	    free_stroke (stp);
2366     	    gvrender_set_pencolor(job, color);
2367 	    if (fillcolor != color)
2368 		gvrender_set_fillcolor(job, fillcolor);
2369 	    if (bz.sflag) {
2370 		arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2371 	    }
2372 	    if (bz.eflag) {
2373 		arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2374 	    }
2375 	}
2376 	/* if more than one color - then generate parallel beziers, one per color */
2377 	else if (numc) {
2378 	    /* calculate and save offset vector spline and initialize first offset spline */
2379 	    tmpspl.size = offspl.size = ED_spl(e)->size;
2380 	    offspl.list = malloc(sizeof(bezier) * offspl.size);
2381 	    tmpspl.list = malloc(sizeof(bezier) * tmpspl.size);
2382 	    numc2 = (2 + numc) / 2.0;
2383 	    for (i = 0; i < offspl.size; i++) {
2384 		bz = ED_spl(e)->list[i];
2385 		tmpspl.list[i].size = offspl.list[i].size = bz.size;
2386 		offlist = offspl.list[i].list = malloc(sizeof(pointf) * bz.size);
2387 		tmplist = tmpspl.list[i].list = malloc(sizeof(pointf) * bz.size);
2388 		pf3 = bz.list[0];
2389 		for (j = 0; j < bz.size - 1; j += 3) {
2390 		    pf0 = pf3;
2391 		    pf1 = bz.list[j + 1];
2392 		    /* calculate perpendicular vectors for each bezier point */
2393 		    if (j == 0)	/* first segment, no previous pf2 */
2394 			offlist[j] = computeoffset_p(pf0, pf1, SEP);
2395 		    else	/* i.e. pf2 is available from previous segment */
2396 			offlist[j] = computeoffset_p(pf2, pf1, SEP);
2397 		    pf2 = bz.list[j + 2];
2398 		    pf3 = bz.list[j + 3];
2399 		    offlist[j + 1] = offlist[j + 2] =
2400 			computeoffset_qr(pf0, pf1, pf2, pf3, SEP);
2401 		    /* initialize tmpspl to outermost position */
2402 		    tmplist[j].x = pf0.x - numc2 * offlist[j].x;
2403 		    tmplist[j].y = pf0.y - numc2 * offlist[j].y;
2404 		    tmplist[j + 1].x = pf1.x - numc2 * offlist[j + 1].x;
2405 		    tmplist[j + 1].y = pf1.y - numc2 * offlist[j + 1].y;
2406 		    tmplist[j + 2].x = pf2.x - numc2 * offlist[j + 2].x;
2407 		    tmplist[j + 2].y = pf2.y - numc2 * offlist[j + 2].y;
2408 		}
2409 		/* last segment, no next pf1 */
2410 		offlist[j] = computeoffset_p(pf2, pf3, SEP);
2411 		tmplist[j].x = pf3.x - numc2 * offlist[j].x;
2412 		tmplist[j].y = pf3.y - numc2 * offlist[j].y;
2413 	    }
2414 	    lastcolor = headcolor = tailcolor = color;
2415 	    colors = strdup(color);
2416 	    for (cnum = 0, color = strtok(colors, ":"); color;
2417 		cnum++, color = strtok(0, ":")) {
2418 		if (!color[0])
2419 		    color = DEFAULT_COLOR;
2420 		if (color != lastcolor) {
2421 	            if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
2422 		        gvrender_set_pencolor(job, color);
2423 		        gvrender_set_fillcolor(job, color);
2424 		    }
2425 		    lastcolor = color;
2426 		}
2427 		if (cnum == 0)
2428 		    headcolor = tailcolor = color;
2429 		if (cnum == 1)
2430 		    tailcolor = color;
2431 		for (i = 0; i < tmpspl.size; i++) {
2432 		    tmplist = tmpspl.list[i].list;
2433 		    offlist = offspl.list[i].list;
2434 		    for (j = 0; j < tmpspl.list[i].size; j++) {
2435 			tmplist[j].x += offlist[j].x;
2436 			tmplist[j].y += offlist[j].y;
2437 		    }
2438 		    gvrender_beziercurve(job, tmplist, tmpspl.list[i].size,
2439 					 FALSE, FALSE, FALSE);
2440 		}
2441 	    }
2442 	    if (bz.sflag) {
2443 		if (color != tailcolor) {
2444 		    color = tailcolor;
2445 	            if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
2446 		        gvrender_set_pencolor(job, color);
2447 		        gvrender_set_fillcolor(job, color);
2448 		    }
2449 		}
2450 		arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2451 			arrowsize, penwidth, bz.sflag);
2452 	    }
2453 	    if (bz.eflag) {
2454 		if (color != headcolor) {
2455 		    color = headcolor;
2456 	            if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
2457 		        gvrender_set_pencolor(job, color);
2458 		        gvrender_set_fillcolor(job, color);
2459 		    }
2460 		}
2461 		arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2462 			arrowsize, penwidth, bz.eflag);
2463 	    }
2464 	    free(colors);
2465 	    for (i = 0; i < offspl.size; i++) {
2466 		free(offspl.list[i].list);
2467 		free(tmpspl.list[i].list);
2468 	    }
2469 	    free(offspl.list);
2470 	    free(tmpspl.list);
2471 	} else {
2472 	    if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
2473 	        if (color[0]) {
2474 		    gvrender_set_pencolor(job, color);
2475 		    gvrender_set_fillcolor(job, fillcolor);
2476 	        } else {
2477 		    gvrender_set_pencolor(job, DEFAULT_COLOR);
2478 		    if (fillcolor[0])
2479 			gvrender_set_fillcolor(job, fillcolor);
2480 		    else
2481 			gvrender_set_fillcolor(job, DEFAULT_COLOR);
2482 	        }
2483 	    }
2484 	    for (i = 0; i < ED_spl(e)->size; i++) {
2485 		bz = ED_spl(e)->list[i];
2486 		if (job->flags & GVRENDER_DOES_ARROWS) {
2487 		    gvrender_beziercurve(job, bz.list, bz.size, bz.sflag, bz.eflag, FALSE);
2488 		} else {
2489 		    gvrender_beziercurve(job, bz.list, bz.size, FALSE, FALSE, FALSE);
2490 		    if (bz.sflag) {
2491 			arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2492 				arrowsize, penwidth, bz.sflag);
2493 		    }
2494 		    if (bz.eflag) {
2495 			arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2496 				arrowsize, penwidth, bz.eflag);
2497 		    }
2498 		    if ((ED_spl(e)->size>1) && (bz.sflag||bz.eflag) && styles)
2499 			gvrender_set_style(job, styles);
2500 		}
2501 	    }
2502 	}
2503     }
2504 }
2505 
edge_in_box(edge_t * e,boxf b)2506 static boolean edge_in_box(edge_t *e, boxf b)
2507 {
2508     splines *spl;
2509     textlabel_t *lp;
2510 
2511     spl = ED_spl(e);
2512     if (spl && boxf_overlap(spl->bb, b))
2513         return TRUE;
2514 
2515     lp = ED_label(e);
2516     if (lp && overlap_label(lp, b))
2517         return TRUE;
2518 
2519     lp = ED_xlabel(e);
2520     if (lp && lp->set && overlap_label(lp, b))
2521         return TRUE;
2522 
2523     return FALSE;
2524 }
2525 
emit_begin_edge(GVJ_t * job,edge_t * e,char ** styles)2526 static void emit_begin_edge(GVJ_t * job, edge_t * e, char** styles)
2527 {
2528     obj_state_t *obj;
2529     int flags = job->flags;
2530     char *s;
2531     textlabel_t *lab = NULL, *tlab = NULL, *hlab = NULL;
2532     pointf *pbs = NULL;
2533     int	i, nump, *pbs_n = NULL, pbs_poly_n = 0;
2534     char* dflt_url = NULL;
2535     char* dflt_target = NULL;
2536     double penwidth;
2537 
2538     obj = push_obj_state(job);
2539     obj->type = EDGE_OBJTYPE;
2540     obj->u.e = e;
2541     obj->emit_state = EMIT_EDRAW;
2542     if (ED_label(e) && !ED_label(e)->html && mapBool(agget(e,"labelaligned"),FALSE))
2543 	obj->labeledgealigned = TRUE;
2544 
2545     /* We handle the edge style and penwidth here because the width
2546      * is needed below for calculating polygonal image maps
2547      */
2548     if (styles && ED_spl(e)) gvrender_set_style(job, styles);
2549 
2550     if (E_penwidth && ((s=agxget(e,E_penwidth)) && s[0])) {
2551 	penwidth = late_double(e, E_penwidth, 1.0, 0.0);
2552 	gvrender_set_penwidth(job, penwidth);
2553     }
2554 
2555     if (flags & GVRENDER_DOES_Z) {
2556         /* obj->tail_z = late_double(agtail(e), N_z, 0.0, -1000.0); */
2557         /* obj->head_z = late_double(aghead(e), N_z, 0.0, -MAXFLOAT); */
2558 	if (GD_odim(agraphof(agtail(e))) >=3) {
2559             obj->tail_z = POINTS(ND_pos(agtail(e))[2]);
2560             obj->head_z = POINTS(ND_pos(aghead(e))[2]);
2561 	} else {
2562             obj->tail_z = obj->head_z = 0.0;
2563 	}
2564     }
2565 
2566     if (flags & GVRENDER_DOES_LABELS) {
2567 	if ((lab = ED_label(e)))
2568 	    obj->label = lab->text;
2569 	obj->taillabel = obj->headlabel = obj->xlabel = obj->label;
2570 	if ((tlab = ED_xlabel(e)))
2571 	    obj->xlabel = tlab->text;
2572 	if ((tlab = ED_tail_label(e)))
2573 	    obj->taillabel = tlab->text;
2574 	if ((hlab = ED_head_label(e)))
2575 	    obj->headlabel = hlab->text;
2576     }
2577 
2578     if (flags & GVRENDER_DOES_MAPS) {
2579 	agxbuf xb;
2580 	unsigned char xbuf[SMALLBUF];
2581 
2582 	agxbinit(&xb, SMALLBUF, xbuf);
2583 	s = getObjId (job, e, &xb);
2584 	obj->id = strdup_and_subst_obj(s, (void*)e);
2585 	agxbfree(&xb);
2586 
2587         if (((s = agget(e, "href")) && s[0]) || ((s = agget(e, "URL")) && s[0]))
2588             dflt_url = strdup_and_subst_obj(s, (void*)e);
2589 	if (((s = agget(e, "edgehref")) && s[0]) || ((s = agget(e, "edgeURL")) && s[0]))
2590             obj->url = strdup_and_subst_obj(s, (void*)e);
2591 	else if (dflt_url)
2592 	    obj->url = strdup(dflt_url);
2593 	if (((s = agget(e, "labelhref")) && s[0]) || ((s = agget(e, "labelURL")) && s[0]))
2594             obj->labelurl = strdup_and_subst_obj(s, (void*)e);
2595 	else if (dflt_url)
2596 	    obj->labelurl = strdup(dflt_url);
2597 	if (((s = agget(e, "tailhref")) && s[0]) || ((s = agget(e, "tailURL")) && s[0])) {
2598             obj->tailurl = strdup_and_subst_obj(s, (void*)e);
2599             obj->explicit_tailurl = TRUE;
2600 	}
2601 	else if (dflt_url)
2602 	    obj->tailurl = strdup(dflt_url);
2603 	if (((s = agget(e, "headhref")) && s[0]) || ((s = agget(e, "headURL")) && s[0])) {
2604             obj->headurl = strdup_and_subst_obj(s, (void*)e);
2605             obj->explicit_headurl = TRUE;
2606 	}
2607 	else if (dflt_url)
2608 	    obj->headurl = strdup(dflt_url);
2609     }
2610 
2611     if (flags & GVRENDER_DOES_TARGETS) {
2612         if ((s = agget(e, "target")) && s[0])
2613             dflt_target = strdup_and_subst_obj(s, (void*)e);
2614         if ((s = agget(e, "edgetarget")) && s[0]) {
2615 	    obj->explicit_edgetarget = TRUE;
2616             obj->target = strdup_and_subst_obj(s, (void*)e);
2617 	}
2618 	else if (dflt_target)
2619 	    obj->target = strdup(dflt_target);
2620         if ((s = agget(e, "labeltarget")) && s[0])
2621             obj->labeltarget = strdup_and_subst_obj(s, (void*)e);
2622 	else if (dflt_target)
2623 	    obj->labeltarget = strdup(dflt_target);
2624         if ((s = agget(e, "tailtarget")) && s[0]) {
2625             obj->tailtarget = strdup_and_subst_obj(s, (void*)e);
2626 	    obj->explicit_tailtarget = TRUE;
2627 	}
2628 	else if (dflt_target)
2629 	    obj->tailtarget = strdup(dflt_target);
2630         if ((s = agget(e, "headtarget")) && s[0]) {
2631 	    obj->explicit_headtarget = TRUE;
2632             obj->headtarget = strdup_and_subst_obj(s, (void*)e);
2633 	}
2634 	else if (dflt_target)
2635 	    obj->headtarget = strdup(dflt_target);
2636     }
2637 
2638     if (flags & GVRENDER_DOES_TOOLTIPS) {
2639         if (((s = agget(e, "tooltip")) && s[0]) ||
2640             ((s = agget(e, "edgetooltip")) && s[0])) {
2641 	    char* tooltip = preprocessTooltip (s, e);
2642             obj->tooltip = strdup_and_subst_obj(tooltip, (void*)e);
2643 	    free (tooltip);
2644 	    obj->explicit_tooltip = TRUE;
2645 	}
2646 	else if (obj->label)
2647 	    obj->tooltip = strdup(obj->label);
2648 
2649         if ((s = agget(e, "labeltooltip")) && s[0]) {
2650 	    char* tooltip = preprocessTooltip (s, e);
2651             obj->labeltooltip = strdup_and_subst_obj(tooltip, (void*)e);
2652 	    free (tooltip);
2653 	    obj->explicit_labeltooltip = TRUE;
2654 	}
2655 	else if (obj->label)
2656 	    obj->labeltooltip = strdup(obj->label);
2657 
2658         if ((s = agget(e, "tailtooltip")) && s[0]) {
2659 	    char* tooltip = preprocessTooltip (s, e);
2660             obj->tailtooltip = strdup_and_subst_obj(tooltip, (void*)e);
2661 	    free (tooltip);
2662 	    obj->explicit_tailtooltip = TRUE;
2663 	}
2664 	else if (obj->taillabel)
2665 	    obj->tailtooltip = strdup(obj->taillabel);
2666 
2667         if ((s = agget(e, "headtooltip")) && s[0]) {
2668 	    char* tooltip = preprocessTooltip (s, e);
2669             obj->headtooltip = strdup_and_subst_obj(tooltip, (void*)e);
2670 	    free (tooltip);
2671 	    obj->explicit_headtooltip = TRUE;
2672 	}
2673 	else if (obj->headlabel)
2674 	    obj->headtooltip = strdup(obj->headlabel);
2675     }
2676 
2677     free (dflt_url);
2678     free (dflt_target);
2679 
2680     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
2681 	if (ED_spl(e) && (obj->url || obj->tooltip) && (flags & GVRENDER_DOES_MAP_POLYGON)) {
2682 	    int ns;
2683 	    splines *spl;
2684 	    double w2 = MAX(job->obj->penwidth/2.0,2.0);
2685 
2686 	    spl = ED_spl(e);
2687 	    ns = spl->size; /* number of splines */
2688 	    for (i = 0; i < ns; i++)
2689 		map_output_bspline (&pbs, &pbs_n, &pbs_poly_n, spl->list+i, w2);
2690 	    obj->url_bsplinemap_poly_n = pbs_poly_n;
2691 	    obj->url_bsplinemap_n = pbs_n;
2692 	    if (! (flags & GVRENDER_DOES_TRANSFORM)) {
2693     		for ( nump = 0, i = 0; i < pbs_poly_n; i++)
2694         	    nump += pbs_n[i];
2695 		gvrender_ptf_A(job, pbs, pbs, nump);
2696 	    }
2697 	    obj->url_bsplinemap_p = pbs;
2698 	    obj->url_map_shape = MAP_POLYGON;
2699 	    obj->url_map_p = pbs;
2700 	    obj->url_map_n = pbs_n[0];
2701 	}
2702     }
2703 
2704     gvrender_begin_edge(job, e);
2705     if (obj->url || obj->explicit_tooltip)
2706 	gvrender_begin_anchor(job,
2707 		obj->url, obj->tooltip, obj->target, obj->id);
2708 }
2709 
2710 static void
emit_edge_label(GVJ_t * job,textlabel_t * lbl,emit_state_t lkind,int explicit,char * url,char * tooltip,char * target,char * id,splines * spl)2711 emit_edge_label(GVJ_t* job, textlabel_t* lbl, emit_state_t lkind, int explicit,
2712     char* url, char* tooltip, char* target, char *id, splines* spl)
2713 {
2714     int flags = job->flags;
2715     emit_state_t old_emit_state;
2716     char* newid;
2717     char* type;
2718 
2719     if ((lbl == NULL) || !(lbl->set)) return;
2720     if (id) { /* non-NULL if needed */
2721 	newid = N_NEW(strlen(id) + sizeof("-headlabel"),char);
2722 	switch (lkind) {
2723 	case EMIT_ELABEL :
2724 	    type = "label";
2725 	    break;
2726 	case EMIT_HLABEL :
2727 	    type = "headlabel";
2728 	    break;
2729 	case EMIT_TLABEL :
2730 	    type = "taillabel";
2731 	    break;
2732 	default :
2733 	    assert (0);
2734 	    break;
2735 	}
2736 	sprintf (newid, "%s-%s", id, type);
2737     }
2738     else
2739 	newid = NULL;
2740     old_emit_state = job->obj->emit_state;
2741     job->obj->emit_state = lkind;
2742     if ((url || explicit) && !(flags & EMIT_CLUSTERS_LAST)) {
2743 	map_label(job, lbl);
2744 	gvrender_begin_anchor(job, url, tooltip, target, newid);
2745     }
2746     emit_label(job, lkind, lbl);
2747     if (spl) emit_attachment(job, lbl, spl);
2748     if (url || explicit) {
2749 	if (flags & EMIT_CLUSTERS_LAST) {
2750 	    map_label(job, lbl);
2751 	    gvrender_begin_anchor(job, url, tooltip, target, newid);
2752 	}
2753 	gvrender_end_anchor(job);
2754     }
2755     if (newid) free (newid);
2756     job->obj->emit_state = old_emit_state;
2757 }
2758 
2759 /* nodeIntersect:
2760  * Common logic for setting hot spots at the beginning and end of
2761  * an edge.
2762  * If we are given a value (url, tooltip, target) explicitly set for
2763  * the head/tail, we use that.
2764  * Otherwise, if we are given a value explicitly set for the edge,
2765  * we use that.
2766  * Otherwise, we use whatever the argument value is.
2767  * We also note whether or not the tooltip was explicitly set.
2768  * If the url is non-NULL or the tooltip was explicit, we set
2769  * a hot spot around point p.
2770  */
nodeIntersect(GVJ_t * job,pointf p,boolean explicit_iurl,char * iurl,boolean explicit_itooltip,char * itooltip,boolean explicit_itarget,char * itarget)2771 static void nodeIntersect (GVJ_t * job, pointf p,
2772     boolean explicit_iurl, char* iurl,
2773     boolean explicit_itooltip, char* itooltip,
2774     boolean explicit_itarget, char* itarget)
2775 {
2776     obj_state_t *obj = job->obj;
2777     char* url;
2778 #if 0
2779     char* tooltip;
2780     char* target;
2781 #endif
2782     boolean explicit;
2783 
2784     if (explicit_iurl) url = iurl;
2785     else url = obj->url;
2786     if (explicit_itooltip) {
2787 #if 0
2788 	tooltip = itooltip;
2789 #endif
2790 	explicit = TRUE;
2791     }
2792     else if (obj->explicit_tooltip) {
2793 #if 0
2794 	tooltip = obj->tooltip;
2795 #endif
2796 	explicit = TRUE;
2797     }
2798     else {
2799 #if 0
2800 	tooltip = itooltip;
2801 #endif
2802 	explicit = FALSE;
2803     }
2804 #if 0
2805     if (explicit_itarget)
2806 	target = itarget;
2807     else if (obj->explicit_edgetarget)
2808 	target = obj->target;
2809     else
2810 	target = itarget;
2811 #endif
2812 
2813     if (url || explicit) {
2814 	map_point(job, p);
2815 #if 0
2816 /* this doesn't work because there is nothing contained in the anchor */
2817 	gvrender_begin_anchor(job, url, tooltip, target, obj->id);
2818 	gvrender_end_anchor(job);
2819 #endif
2820     }
2821 }
2822 
emit_end_edge(GVJ_t * job)2823 static void emit_end_edge(GVJ_t * job)
2824 {
2825     obj_state_t *obj = job->obj;
2826     edge_t *e = obj->u.e;
2827     int i, nump;
2828 
2829     if (obj->url || obj->explicit_tooltip) {
2830 	gvrender_end_anchor(job);
2831 	if (obj->url_bsplinemap_poly_n) {
2832 	    for ( nump = obj->url_bsplinemap_n[0], i = 1; i < obj->url_bsplinemap_poly_n; i++) {
2833 		/* additional polygon maps around remaining bezier pieces */
2834 		obj->url_map_n = obj->url_bsplinemap_n[i];
2835 		obj->url_map_p = &(obj->url_bsplinemap_p[nump]);
2836 		gvrender_begin_anchor(job,
2837 			obj->url, obj->tooltip, obj->target, obj->id);
2838 		gvrender_end_anchor(job);
2839 		nump += obj->url_bsplinemap_n[i];
2840 	    }
2841 	}
2842     }
2843     obj->url_map_n = 0;       /* null out copy so that it doesn't get freed twice */
2844     obj->url_map_p = NULL;
2845 
2846     if (ED_spl(e)) {
2847 	pointf p;
2848 	bezier bz;
2849 
2850 	/* process intersection with tail node */
2851 	bz = ED_spl(e)->list[0];
2852 	if (bz.sflag) /* Arrow at start of splines */
2853 	    p = bz.sp;
2854 	else /* No arrow at start of splines */
2855 	    p = bz.list[0];
2856 	nodeIntersect (job, p, obj->explicit_tailurl, obj->tailurl,
2857 	    obj->explicit_tailtooltip, obj->tailtooltip,
2858 	    obj->explicit_tailtarget, obj->tailtarget);
2859 
2860 	/* process intersection with head node */
2861 	bz = ED_spl(e)->list[ED_spl(e)->size - 1];
2862 	if (bz.eflag) /* Arrow at end of splines */
2863 	    p = bz.ep;
2864 	else /* No arrow at end of splines */
2865 	    p = bz.list[bz.size - 1];
2866 	nodeIntersect (job, p, obj->explicit_headurl, obj->headurl,
2867 	    obj->explicit_headtooltip, obj->headtooltip,
2868 	    obj->explicit_headtarget, obj->headtarget);
2869     }
2870 
2871     emit_edge_label(job, ED_label(e), EMIT_ELABEL,
2872 	obj->explicit_labeltooltip,
2873 	obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
2874 	((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
2875     emit_edge_label(job, ED_xlabel(e), EMIT_ELABEL,
2876 	obj->explicit_labeltooltip,
2877 	obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
2878 	((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
2879     emit_edge_label(job, ED_head_label(e), EMIT_HLABEL,
2880 	obj->explicit_headtooltip,
2881 	obj->headurl, obj->headtooltip, obj->headtarget, obj->id,
2882 	0);
2883     emit_edge_label(job, ED_tail_label(e), EMIT_TLABEL,
2884 	obj->explicit_tailtooltip,
2885 	obj->tailurl, obj->tailtooltip, obj->tailtarget, obj->id,
2886 	0);
2887 
2888     gvrender_end_edge(job);
2889     pop_obj_state(job);
2890 }
2891 
emit_edge(GVJ_t * job,edge_t * e)2892 static void emit_edge(GVJ_t * job, edge_t * e)
2893 {
2894     char *s;
2895     char *style;
2896     char **styles = 0;
2897     char **sp;
2898     char *p;
2899 
2900     if (edge_in_box(e, job->clip) && edge_in_layer(job, agraphof(aghead(e)), e) ) {
2901 
2902 	s = malloc(strlen(agnameof(agtail(e))) + 2 + strlen(agnameof(aghead(e))) + 1);
2903 	strcpy(s,agnameof(agtail(e)));
2904 	if (agisdirected(agraphof(aghead(e))))
2905 
2906 	    strcat(s,"->");
2907 	else
2908 	    strcat(s,"--");
2909 	strcat(s,agnameof(aghead(e)));
2910 	gvrender_comment(job, s);
2911 	free(s);
2912 
2913 	s = late_string(e, E_comment, "");
2914 	if (s[0])
2915 	    gvrender_comment(job, s);
2916 
2917 	style = late_string(e, E_style, "");
2918 	/* We shortcircuit drawing an invisible edge because the arrowhead
2919 	 * code resets the style to solid, and most of the code generators
2920 	 * (except PostScript) won't honor a previous style of invis.
2921 	 */
2922 	if (style[0]) {
2923 	    styles = parse_style(style);
2924 	    sp = styles;
2925 	    while ((p = *sp++)) {
2926 		if (streq(p, "invis")) return;
2927 	    }
2928 	}
2929 
2930 	emit_begin_edge(job, e, styles);
2931 	emit_edge_graphics (job, e, styles);
2932 	emit_end_edge(job);
2933     }
2934 }
2935 
2936 static char adjust[] = {'l', 'n', 'r'};
2937 
2938 static void
expandBB(boxf * bb,pointf p)2939 expandBB (boxf* bb, pointf p)
2940 {
2941     if (p.x > bb->UR.x)
2942 	bb->UR.x = p.x;
2943     if (p.x < bb->LL.x)
2944 	bb->LL.x = p.x;
2945     if (p.y > bb->UR.y)
2946 	bb->UR.y = p.y;
2947     if (p.y < bb->LL.y)
2948 	bb->LL.y = p.y;
2949 }
2950 
2951 static boxf
ptsBB(xdot_point * inpts,int numpts,boxf * bb)2952 ptsBB (xdot_point* inpts, int numpts, boxf* bb)
2953 {
2954     boxf opbb;
2955     int i;
2956 
2957     opbb.LL.x = opbb.UR.x = inpts->x;
2958     opbb.LL.y = opbb.UR.y = inpts->y;
2959     for (i = 1; i < numpts; i++) {
2960 	inpts++;
2961 	if (inpts->x < opbb.LL.x)
2962 	    opbb.LL.x = inpts->x;
2963 	else if (inpts->x > opbb.UR.x)
2964 	    opbb.UR.x = inpts->x;
2965 	if (inpts->y < opbb.LL.y)
2966 	    opbb.LL.y = inpts->y;
2967 	else if (inpts->y > opbb.UR.y)
2968 	    opbb.UR.y = inpts->y;
2969 
2970     }
2971     expandBB (bb, opbb.LL);
2972     expandBB (bb, opbb.UR);
2973     return opbb;
2974 }
2975 
2976 static boxf
textBB(double x,double y,textspan_t * span)2977 textBB (double x, double y, textspan_t* span)
2978 {
2979     boxf bb;
2980     pointf sz = span->size;
2981 
2982     switch (span->just) {
2983     case 'l':
2984 	bb.LL.x = x;
2985 	bb.UR.x = bb.LL.x + sz.x;
2986 	break;
2987     case 'n':
2988 	bb.LL.x = x - sz.x / 2.0;
2989 	bb.UR.x = x + sz.x / 2.0;
2990 	break;
2991     case 'r':
2992 	bb.UR.x = x;
2993 	bb.LL.x = bb.UR.x - sz.x;
2994 	break;
2995     }
2996     bb.UR.y = y + span->yoffset_layout;
2997     bb.LL.y = bb.UR.y - sz.y;
2998     return bb;
2999 }
3000 
3001 static void
freePara(exdot_op * op)3002 freePara (exdot_op* op)
3003 {
3004     if (op->op.kind == xd_text)
3005 	free_textspan (op->span, 1);
3006 }
3007 
xdotBB(Agraph_t * g)3008 boxf xdotBB (Agraph_t* g)
3009 {
3010     GVC_t *gvc = GD_gvc(g);
3011     exdot_op* op;
3012     int i;
3013     double fontsize = 0.0;
3014     char* fontname = NULL;
3015     pointf pts[2];
3016     /* pointf sz; */
3017     boxf bb0;
3018     boxf bb = GD_bb(g);
3019     xdot* xd = (xdot*)GD_drawing(g)->xdots;
3020     textfont_t tf, null_tf = {NULL,NULL,NULL,0.0,0,0};
3021     int fontflags = 0;
3022 
3023     if (!xd) return bb;
3024 
3025     if ((bb.LL.x == bb.UR.x) && (bb.LL.y == bb.UR.y)) {
3026 	bb.LL.x = bb.LL.y = MAXDOUBLE;
3027 	bb.UR.x = bb.UR.y = -MAXDOUBLE;
3028     }
3029 
3030     op = (exdot_op*)(xd->ops);
3031     for (i = 0; i < xd->cnt; i++) {
3032 	tf = null_tf;
3033 	switch (op->op.kind) {
3034 	case xd_filled_ellipse :
3035 	case xd_unfilled_ellipse :
3036 	    pts[0].x = op->op.u.ellipse.x - op->op.u.ellipse.w;
3037 	    pts[0].y = op->op.u.ellipse.y - op->op.u.ellipse.h;
3038 	    pts[1].x = op->op.u.ellipse.x + op->op.u.ellipse.w;
3039 	    pts[1].y = op->op.u.ellipse.y + op->op.u.ellipse.h;
3040 	    op->bb.LL = pts[0];
3041 	    op->bb.UR = pts[1];
3042 	    expandBB (&bb, pts[0]);
3043 	    expandBB (&bb, pts[1]);
3044 	    break;
3045 	case xd_filled_polygon :
3046 	case xd_unfilled_polygon :
3047 	    op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
3048 	    break;
3049 	case xd_filled_bezier :
3050 	case xd_unfilled_bezier :
3051 	    op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
3052 	    break;
3053 	case xd_polyline :
3054 	    op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
3055 	    break;
3056 	case xd_text :
3057 	    op->span = NEW(textspan_t);
3058 	    op->span->str = strdup (op->op.u.text.text);
3059 	    op->span->just = adjust [op->op.u.text.align];
3060 	    tf.name = fontname;
3061 	    tf.size = fontsize;
3062 	    tf.flags = fontflags;
3063             op->span->font = dtinsert(gvc->textfont_dt, &tf);
3064 	    textspan_size (gvc, op->span);
3065 	    bb0 = textBB (op->op.u.text.x, op->op.u.text.y, op->span);
3066 	    op->bb = bb0;
3067 	    expandBB (&bb, bb0.LL);
3068 	    expandBB (&bb, bb0.UR);
3069 	    if (!xd->freefunc)
3070 		xd->freefunc = (freefunc_t)freePara;
3071 	    break;
3072 	case xd_font :
3073 	    fontsize = op->op.u.font.size;
3074 	    fontname = op->op.u.font.name;
3075 	    break;
3076 	case xd_fontchar :
3077 	    fontflags = op->op.u.fontchar;
3078 	    break;
3079 	default :
3080 	    break;
3081 	}
3082 	op++;
3083     }
3084     return bb;
3085 }
3086 
init_gvc(GVC_t * gvc,graph_t * g)3087 static void init_gvc(GVC_t * gvc, graph_t * g)
3088 {
3089     double xf, yf;
3090     char *p;
3091     int i;
3092 
3093     gvc->g = g;
3094 
3095     /* margins */
3096     gvc->graph_sets_margin = FALSE;
3097     if ((p = agget(g, "margin"))) {
3098         i = sscanf(p, "%lf,%lf", &xf, &yf);
3099         if (i > 0) {
3100             gvc->margin.x = gvc->margin.y = xf * POINTS_PER_INCH;
3101             if (i > 1)
3102                 gvc->margin.y = yf * POINTS_PER_INCH;
3103             gvc->graph_sets_margin = TRUE;
3104         }
3105     }
3106 
3107     /* pad */
3108     gvc->graph_sets_pad = FALSE;
3109     if ((p = agget(g, "pad"))) {
3110         i = sscanf(p, "%lf,%lf", &xf, &yf);
3111         if (i > 0) {
3112             gvc->pad.x = gvc->pad.y = xf * POINTS_PER_INCH;
3113             if (i > 1)
3114                 gvc->pad.y = yf * POINTS_PER_INCH;
3115             gvc->graph_sets_pad = TRUE;
3116         }
3117     }
3118 
3119     /* pagesize */
3120     gvc->graph_sets_pageSize = FALSE;
3121     gvc->pageSize = GD_drawing(g)->page;
3122     if ((GD_drawing(g)->page.x > 0.001) && (GD_drawing(g)->page.y > 0.001))
3123         gvc->graph_sets_pageSize = TRUE;
3124 
3125     /* rotation */
3126     if (GD_drawing(g)->landscape)
3127 	gvc->rotation = 90;
3128     else
3129 	gvc->rotation = 0;
3130 
3131     /* pagedir */
3132     gvc->pagedir = "BL";
3133     if ((p = agget(g, "pagedir")) && p[0])
3134             gvc->pagedir = p;
3135 
3136 
3137     /* bounding box */
3138     gvc->bb = GD_bb(g);
3139 
3140     /* clusters have peripheries */
3141     G_peripheries = agfindgraphattr(g, "peripheries");
3142     G_penwidth = agfindgraphattr(g, "penwidth");
3143 
3144     /* default font */
3145     gvc->defaultfontname = late_nnstring(NULL,
3146                 N_fontname, DEFAULT_FONTNAME);
3147     gvc->defaultfontsize = late_double(NULL,
3148                 N_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE);
3149 
3150     /* default line style */
3151     gvc->defaultlinestyle = defaultlinestyle;
3152 
3153     gvc->graphname = agnameof(g);
3154 }
3155 
init_job_pad(GVJ_t * job)3156 static void init_job_pad(GVJ_t *job)
3157 {
3158     GVC_t *gvc = job->gvc;
3159 
3160     if (gvc->graph_sets_pad) {
3161 	job->pad = gvc->pad;
3162     }
3163     else {
3164 	switch (job->output_lang) {
3165 	case GVRENDER_PLUGIN:
3166 	    job->pad.x = job->pad.y = job->render.features->default_pad;
3167 	    break;
3168 	default:
3169 	    job->pad.x = job->pad.y = DEFAULT_GRAPH_PAD;
3170 	    break;
3171 	}
3172     }
3173 }
3174 
init_job_margin(GVJ_t * job)3175 static void init_job_margin(GVJ_t *job)
3176 {
3177     GVC_t *gvc = job->gvc;
3178 
3179     if (gvc->graph_sets_margin) {
3180 	job->margin = gvc->margin;
3181     }
3182     else {
3183         /* set default margins depending on format */
3184         switch (job->output_lang) {
3185         case GVRENDER_PLUGIN:
3186             job->margin = job->device.features->default_margin;
3187             break;
3188         case HPGL: case PCL: case MIF: case METAPOST: case VTX: case QPDF:
3189             job->margin.x = job->margin.y = DEFAULT_PRINT_MARGIN;
3190             break;
3191         default:
3192             job->margin.x = job->margin.y = DEFAULT_EMBED_MARGIN;
3193             break;
3194         }
3195     }
3196 
3197 }
3198 
init_job_dpi(GVJ_t * job,graph_t * g)3199 static void init_job_dpi(GVJ_t *job, graph_t *g)
3200 {
3201     GVJ_t *firstjob = job->gvc->active_jobs;
3202 
3203     if (GD_drawing(g)->dpi != 0) {
3204         job->dpi.x = job->dpi.y = (double)(GD_drawing(g)->dpi);
3205     }
3206     else if (firstjob && firstjob->device_sets_dpi) {
3207         job->dpi = firstjob->device_dpi;   /* some devices set dpi in initialize() */
3208     }
3209     else {
3210         /* set default margins depending on format */
3211         switch (job->output_lang) {
3212         case GVRENDER_PLUGIN:
3213             job->dpi = job->device.features->default_dpi;
3214             break;
3215         default:
3216             job->dpi.x = job->dpi.y = (double)(DEFAULT_DPI);
3217             break;
3218         }
3219     }
3220 }
3221 
init_job_viewport(GVJ_t * job,graph_t * g)3222 static void init_job_viewport(GVJ_t * job, graph_t * g)
3223 {
3224     GVC_t *gvc = job->gvc;
3225     pointf LL, UR, size, sz;
3226     double X, Y, Z, x, y;
3227     int rv;
3228     Agnode_t *n;
3229     char *str, *nodename = NULL, *junk = NULL;
3230 
3231     UR = gvc->bb.UR;
3232     LL = gvc->bb.LL;
3233     job->bb.LL.x = LL.x - job->pad.x;           /* job->bb is bb of graph and padding - graph units */
3234     job->bb.LL.y = LL.y - job->pad.y;
3235     job->bb.UR.x = UR.x + job->pad.x;
3236     job->bb.UR.y = UR.y + job->pad.y;
3237     sz.x = job->bb.UR.x - job->bb.LL.x;   /* size, including padding - graph units */
3238     sz.y = job->bb.UR.y - job->bb.LL.y;
3239 
3240     /* determine final drawing size and scale to apply. */
3241     /* N.B. size given by user is not rotated by landscape mode */
3242     /* start with "natural" size of layout */
3243 
3244     Z = 1.0;
3245     if (GD_drawing(g)->size.x > 0.001 && GD_drawing(g)->size.y > 0.001) { /* graph size was given by user... */
3246 	size = GD_drawing(g)->size;
3247 	if (sz.x == 0) sz.x = size.x;
3248 	if (sz.y == 0) sz.y = size.y;
3249 	if ((size.x < sz.x) || (size.y < sz.y) /* drawing is too big (in either axis) ... */
3250 	    || ((GD_drawing(g)->filled) /* or ratio=filled requested and ... */
3251 		&& (size.x > sz.x) && (size.y > sz.y))) /* drawing is too small (in both axes) ... */
3252 	    Z = MIN(size.x/sz.x, size.y/sz.y);
3253     }
3254 
3255     /* default focus, in graph units = center of bb */
3256     x = (LL.x + UR.x) / 2.;
3257     y = (LL.y + UR.y) / 2.;
3258 
3259     /* rotate and scale bb to give default absolute size in points*/
3260     job->rotation = job->gvc->rotation;
3261     X = sz.x * Z;
3262     Y = sz.y * Z;
3263 
3264     /* user can override */
3265     if ((str = agget(g, "viewport"))) {
3266         nodename = malloc(strlen(str)+1);
3267         junk = malloc(strlen(str)+1);
3268 	rv = sscanf(str, "%lf,%lf,%lf,\'%[^\']\'", &X, &Y, &Z, nodename);
3269 	if (rv == 4) {
3270 	    n = agfindnode(g->root, nodename);
3271 	    if (n) {
3272 		x = ND_coord(n).x;
3273 		y = ND_coord(n).y;
3274 	    }
3275 	}
3276 	else {
3277 	    rv = sscanf(str, "%lf,%lf,%lf,%[^,]%s", &X, &Y, &Z, nodename, junk);
3278 	    if (rv == 4) {
3279                 n = agfindnode(g->root, nodename);
3280                 if (n) {
3281                     x = ND_coord(n).x;
3282                     y = ND_coord(n).y;
3283 		}
3284 	    }
3285 	    else {
3286 	        rv = sscanf(str, "%lf,%lf,%lf,%lf,%lf", &X, &Y, &Z, &x, &y);
3287 	    }
3288         }
3289 	free (nodename);
3290 	free (junk);
3291     }
3292     /* rv is ignored since args retain previous values if not scanned */
3293 
3294     /* job->view gives port size in graph units, unscaled or rotated
3295      * job->zoom gives scaling factor.
3296      * job->focus gives the position in the graph of the center of the port
3297      */
3298     job->view.x = X;
3299     job->view.y = Y;
3300     job->zoom = Z;              /* scaling factor */
3301     job->focus.x = x;
3302     job->focus.y = y;
3303 #if 0
3304 fprintf(stderr, "view=%g,%g, zoom=%g, focus=%g,%g\n",
3305 	job->view.x, job->view.y,
3306 	job->zoom,
3307 	job->focus.x, job->focus.y);
3308 #endif
3309 }
3310 
emit_cluster_colors(GVJ_t * job,graph_t * g)3311 static void emit_cluster_colors(GVJ_t * job, graph_t * g)
3312 {
3313     graph_t *sg;
3314     int c;
3315     char *str;
3316 
3317     for (c = 1; c <= GD_n_cluster(g); c++) {
3318 	sg = GD_clust(g)[c];
3319 	emit_cluster_colors(job, sg);
3320 	if (((str = agget(sg, "color")) != 0) && str[0])
3321 	    gvrender_set_pencolor(job, str);
3322 	if (((str = agget(sg, "pencolor")) != 0) && str[0])
3323 	    gvrender_set_pencolor(job, str);
3324 	if (((str = agget(sg, "bgcolor")) != 0) && str[0])
3325 	    gvrender_set_pencolor(job, str);
3326 	if (((str = agget(sg, "fillcolor")) != 0) && str[0])
3327 	    gvrender_set_fillcolor(job, str);
3328 	if (((str = agget(sg, "fontcolor")) != 0) && str[0])
3329 	    gvrender_set_pencolor(job, str);
3330     }
3331 }
3332 
emit_colors(GVJ_t * job,graph_t * g)3333 static void emit_colors(GVJ_t * job, graph_t * g)
3334 {
3335     node_t *n;
3336     edge_t *e;
3337     char *str, *colors;
3338 
3339     gvrender_set_fillcolor(job, DEFAULT_FILL);
3340     if (((str = agget(g, "bgcolor")) != 0) && str[0])
3341 	gvrender_set_fillcolor(job, str);
3342     if (((str = agget(g, "fontcolor")) != 0) && str[0])
3343 	gvrender_set_pencolor(job, str);
3344 
3345     emit_cluster_colors(job, g);
3346     for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3347 	if (((str = agget(n, "color")) != 0) && str[0])
3348 	    gvrender_set_pencolor(job, str);
3349 	if (((str = agget(n, "pencolor")) != 0) && str[0])
3350 	    gvrender_set_fillcolor(job, str);
3351 	if (((str = agget(n, "fillcolor")) != 0) && str[0]) {
3352 	    if (strchr(str, ':')) {
3353 		colors = strdup(str);
3354 		for (str = strtok(colors, ":"); str;
3355 		    str = strtok(0, ":")) {
3356 		    if (str[0])
3357 			gvrender_set_pencolor(job, str);
3358 		}
3359 		free(colors);
3360 	    }
3361 	    else {
3362 		gvrender_set_pencolor(job, str);
3363 	    }
3364 	}
3365 	if (((str = agget(n, "fontcolor")) != 0) && str[0])
3366 	    gvrender_set_pencolor(job, str);
3367 	for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3368 	    if (((str = agget(e, "color")) != 0) && str[0]) {
3369 		if (strchr(str, ':')) {
3370 		    colors = strdup(str);
3371 		    for (str = strtok(colors, ":"); str;
3372 			str = strtok(0, ":")) {
3373 			if (str[0])
3374 			    gvrender_set_pencolor(job, str);
3375 		    }
3376 		    free(colors);
3377 		}
3378 		else {
3379 		    gvrender_set_pencolor(job, str);
3380 		}
3381 	    }
3382 	    if (((str = agget(e, "fontcolor")) != 0) && str[0])
3383 		gvrender_set_pencolor(job, str);
3384 	}
3385     }
3386 }
3387 
emit_view(GVJ_t * job,graph_t * g,int flags)3388 static void emit_view(GVJ_t * job, graph_t * g, int flags)
3389 {
3390     GVC_t * gvc = job->gvc;
3391     node_t *n;
3392     edge_t *e;
3393 
3394     gvc->common.viewNum++;
3395     /* when drawing, lay clusters down before nodes and edges */
3396     if (!(flags & EMIT_CLUSTERS_LAST))
3397 	emit_clusters(job, g, flags);
3398     if (flags & EMIT_SORTED) {
3399 	/* output all nodes, then all edges */
3400 	gvrender_begin_nodes(job);
3401 	for (n = agfstnode(g); n; n = agnxtnode(g, n))
3402 	    emit_node(job, n);
3403 	gvrender_end_nodes(job);
3404 	gvrender_begin_edges(job);
3405 	for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3406 	    for (e = agfstout(g, n); e; e = agnxtout(g, e))
3407 		emit_edge(job, e);
3408 	}
3409 	gvrender_end_edges(job);
3410     } else if (flags & EMIT_EDGE_SORTED) {
3411 	/* output all edges, then all nodes */
3412 	gvrender_begin_edges(job);
3413 	for (n = agfstnode(g); n; n = agnxtnode(g, n))
3414 	    for (e = agfstout(g, n); e; e = agnxtout(g, e))
3415 		emit_edge(job, e);
3416 	gvrender_end_edges(job);
3417 	gvrender_begin_nodes(job);
3418 	for (n = agfstnode(g); n; n = agnxtnode(g, n))
3419 	    emit_node(job, n);
3420 	gvrender_end_nodes(job);
3421     } else if (flags & EMIT_PREORDER) {
3422 	gvrender_begin_nodes(job);
3423 	for (n = agfstnode(g); n; n = agnxtnode(g, n))
3424 	    if (write_node_test(g, n))
3425 		emit_node(job, n);
3426 	gvrender_end_nodes(job);
3427 	gvrender_begin_edges(job);
3428 
3429 	for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3430 	    for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3431 		if (write_edge_test(g, e))
3432 		    emit_edge(job, e);
3433 	    }
3434 	}
3435 	gvrender_end_edges(job);
3436     } else {
3437 	/* output in breadth first graph walk order */
3438 	for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3439 	    emit_node(job, n);
3440 	    for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3441 		emit_node(job, aghead(e));
3442 		emit_edge(job, e);
3443 	    }
3444 	}
3445     }
3446     /* when mapping, detect events on clusters after nodes and edges */
3447     if (flags & EMIT_CLUSTERS_LAST)
3448 	emit_clusters(job, g, flags);
3449 }
3450 
emit_begin_graph(GVJ_t * job,graph_t * g)3451 static void emit_begin_graph(GVJ_t * job, graph_t * g)
3452 {
3453     obj_state_t *obj;
3454 
3455     obj = push_obj_state(job);
3456     obj->type = ROOTGRAPH_OBJTYPE;
3457     obj->u.g = g;
3458     obj->emit_state = EMIT_GDRAW;
3459 
3460     initObjMapData (job, GD_label(g), g);
3461 
3462     gvrender_begin_graph(job, g);
3463 }
3464 
emit_end_graph(GVJ_t * job,graph_t * g)3465 static void emit_end_graph(GVJ_t * job, graph_t * g)
3466 {
3467     gvrender_end_graph(job);
3468     pop_obj_state(job);
3469 }
3470 
3471 #define NotFirstPage(j) (((j)->layerNum>1)||((j)->pagesArrayElem.x > 0)||((j)->pagesArrayElem.x > 0))
3472 
emit_page(GVJ_t * job,graph_t * g)3473 static void emit_page(GVJ_t * job, graph_t * g)
3474 {
3475     obj_state_t *obj = job->obj;
3476     int nump = 0, flags = job->flags;
3477     textlabel_t *lab;
3478     pointf *p = NULL;
3479     char* saveid;
3480     unsigned char buf[SMALLBUF];
3481     agxbuf xb;
3482 
3483     /* For the first page, we can use the values generated in emit_begin_graph.
3484      * For multiple pages, we need to generate a new id.
3485      */
3486     if (NotFirstPage(job)) {
3487 	agxbinit(&xb, SMALLBUF, buf);
3488 	saveid = obj->id;
3489 	layerPagePrefix (job, &xb);
3490 	agxbput (&xb, saveid);
3491 	obj->id = agxbuse(&xb);
3492     }
3493     else
3494 	saveid = NULL;
3495 
3496     setColorScheme (agget (g, "colorscheme"));
3497     setup_page(job, g);
3498     gvrender_begin_page(job);
3499     gvrender_set_pencolor(job, DEFAULT_COLOR);
3500     gvrender_set_fillcolor(job, DEFAULT_FILL);
3501     if ((flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS))
3502 	    && (obj->url || obj->explicit_tooltip)) {
3503 	if (flags & (GVRENDER_DOES_MAP_RECTANGLE | GVRENDER_DOES_MAP_POLYGON)) {
3504 	    if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
3505 		obj->url_map_shape = MAP_RECTANGLE;
3506 		nump = 2;
3507 	    }
3508 	    else {
3509 		obj->url_map_shape = MAP_POLYGON;
3510 		nump = 4;
3511 	    }
3512 	    p = N_NEW(nump, pointf);
3513 	    p[0] = job->pageBox.LL;
3514 	    p[1] = job->pageBox.UR;
3515 	    if (! (flags & (GVRENDER_DOES_MAP_RECTANGLE)))
3516 		rect2poly(p);
3517 	}
3518 	if (! (flags & GVRENDER_DOES_TRANSFORM))
3519 	    gvrender_ptf_A(job, p, p, nump);
3520 	obj->url_map_p = p;
3521 	obj->url_map_n = nump;
3522     }
3523     if ((flags & GVRENDER_DOES_LABELS) && ((lab = GD_label(g))))
3524 	/* do graph label on every page and rely on clipping to show it on the right one(s) */
3525 	obj->label = lab->text;
3526 	/* If EMIT_CLUSTERS_LAST is set, we assume any URL or tooltip
3527 	 * attached to the root graph is emitted either in begin_page
3528 	 * or end_page of renderer.
3529 	 */
3530     if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip)) {
3531 	emit_map_rect(job, job->clip);
3532 	gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3533     }
3534     /* if (numPhysicalLayers(job) == 1) */
3535 	emit_background(job, g);
3536     if (GD_label(g))
3537 	emit_label(job, EMIT_GLABEL, GD_label(g));
3538     if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip))
3539 	gvrender_end_anchor(job);
3540     emit_view(job,g,flags);
3541     gvrender_end_page(job);
3542     if (saveid) {
3543 	agxbfree(&xb);
3544 	obj->id = saveid;
3545     }
3546 }
3547 
emit_graph(GVJ_t * job,graph_t * g)3548 void emit_graph(GVJ_t * job, graph_t * g)
3549 {
3550     node_t *n;
3551     char *s;
3552     int flags = job->flags;
3553     int* lp;
3554 
3555     /* device dpi is now known */
3556     job->scale.x = job->zoom * job->dpi.x / POINTS_PER_INCH;
3557     job->scale.y = job->zoom * job->dpi.y / POINTS_PER_INCH;
3558 
3559     job->devscale.x = job->dpi.x / POINTS_PER_INCH;
3560     job->devscale.y = job->dpi.y / POINTS_PER_INCH;
3561     if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
3562 	job->devscale.y *= -1;
3563 
3564     /* compute current view in graph units */
3565     if (job->rotation) {
3566 	job->view.y = job->width / job->scale.y;
3567 	job->view.x = job->height / job->scale.x;
3568     }
3569     else {
3570 	job->view.x = job->width / job->scale.x;
3571 	job->view.y = job->height / job->scale.y;
3572     }
3573 #if 0
3574 fprintf(stderr,"focus=%g,%g view=%g,%g\n",
3575 	job->focus.x, job->focus.y, job->view.x, job->view.y);
3576 #endif
3577 
3578     s = late_string(g, agattr(g, AGRAPH, "comment", 0), "");
3579     gvrender_comment(job, s);
3580 
3581     job->layerNum = 0;
3582     emit_begin_graph(job, g);
3583 
3584     if (flags & EMIT_COLORS)
3585 	emit_colors(job,g);
3586 
3587     /* reset node state */
3588     for (n = agfstnode(g); n; n = agnxtnode(g, n))
3589 	ND_state(n) = 0;
3590     /* iterate layers */
3591     for (firstlayer(job,&lp); validlayer(job); nextlayer(job,&lp)) {
3592 	if (numPhysicalLayers (job) > 1)
3593 	    gvrender_begin_layer(job);
3594 
3595 	/* iterate pages */
3596 	for (firstpage(job); validpage(job); nextpage(job))
3597 	    emit_page(job, g);
3598 
3599 	if (numPhysicalLayers (job) > 1)
3600 	    gvrender_end_layer(job);
3601     }
3602     emit_end_graph(job, g);
3603 }
3604 
3605 /* support for stderr_once */
free_string_entry(Dict_t * dict,char * key,Dtdisc_t * disc)3606 static void free_string_entry(Dict_t * dict, char *key, Dtdisc_t * disc)
3607 {
3608     free(key);
3609 }
3610 
3611 static Dict_t *strings;
3612 static Dtdisc_t stringdict = {
3613     0,				/* key  - the object itself */
3614     0,				/* size - null-terminated string */
3615     -1,				/* link - allocate separate holder objects  */
3616     NIL(Dtmake_f),
3617     (Dtfree_f) free_string_entry,
3618     NIL(Dtcompar_f),
3619     NIL(Dthash_f),
3620     NIL(Dtmemory_f),
3621     NIL(Dtevent_f)
3622 };
3623 
emit_once(char * str)3624 int emit_once(char *str)
3625 {
3626     if (strings == 0)
3627 	strings = dtopen(&stringdict, Dtoset);
3628     if (!dtsearch(strings, str)) {
3629 	dtinsert(strings, strdup(str));
3630 	return TRUE;
3631     }
3632     return FALSE;
3633 }
3634 
emit_once_reset(void)3635 void emit_once_reset(void)
3636 {
3637     if (strings) {
3638 	dtclose(strings);
3639 	strings = 0;
3640     }
3641 }
3642 
emit_begin_cluster(GVJ_t * job,Agraph_t * sg)3643 static void emit_begin_cluster(GVJ_t * job, Agraph_t * sg)
3644 {
3645     obj_state_t *obj;
3646 
3647     obj = push_obj_state(job);
3648     obj->type = CLUSTER_OBJTYPE;
3649     obj->u.sg = sg;
3650     obj->emit_state = EMIT_CDRAW;
3651 
3652     initObjMapData (job, GD_label(sg), sg);
3653 
3654     gvrender_begin_cluster(job, sg);
3655 }
3656 
emit_end_cluster(GVJ_t * job,Agraph_t * g)3657 static void emit_end_cluster(GVJ_t * job, Agraph_t * g)
3658 {
3659     gvrender_end_cluster(job, g);
3660     pop_obj_state(job);
3661 }
3662 
emit_clusters(GVJ_t * job,Agraph_t * g,int flags)3663 void emit_clusters(GVJ_t * job, Agraph_t * g, int flags)
3664 {
3665     int doPerim, c, istyle, filled;
3666     pointf AF[4];
3667     char *color, *fillcolor, *pencolor, **style, *s;
3668     graph_t *sg;
3669     node_t *n;
3670     edge_t *e;
3671     obj_state_t *obj;
3672     textlabel_t *lab;
3673     int doAnchor;
3674     double penwidth;
3675     char* clrs[2];
3676 
3677     for (c = 1; c <= GD_n_cluster(g); c++) {
3678 	sg = GD_clust(g)[c];
3679 	if (clust_in_layer(job, sg) == FALSE)
3680 	    continue;
3681 	/* when mapping, detect events on clusters after sub_clusters */
3682 	if (flags & EMIT_CLUSTERS_LAST)
3683 	    emit_clusters(job, sg, flags);
3684 	emit_begin_cluster(job, sg);
3685 	obj = job->obj;
3686 	doAnchor = (obj->url || obj->explicit_tooltip);
3687 	setColorScheme (agget (sg, "colorscheme"));
3688 	if (doAnchor && !(flags & EMIT_CLUSTERS_LAST)) {
3689 	    emit_map_rect(job, GD_bb(sg));
3690 	    gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3691 	}
3692 	filled = FALSE;
3693 	istyle = 0;
3694 	if ((style = checkClusterStyle(sg, &istyle))) {
3695 	    gvrender_set_style(job, style);
3696 	    if (istyle & FILLED)
3697 		filled = FILL;
3698 	}
3699 	fillcolor = pencolor = 0;
3700 
3701 	if (GD_gui_state(sg) & GUI_STATE_ACTIVE) {
3702 	    pencolor = late_nnstring(sg, G_activepencolor, DEFAULT_ACTIVEPENCOLOR);
3703 	    fillcolor = late_nnstring(sg, G_activefillcolor, DEFAULT_ACTIVEFILLCOLOR);
3704 	    filled = TRUE;
3705 	}
3706 	else if (GD_gui_state(sg) & GUI_STATE_SELECTED) {
3707 	    pencolor = late_nnstring(sg, G_activepencolor, DEFAULT_SELECTEDPENCOLOR);
3708 	    fillcolor = late_nnstring(sg, G_activefillcolor, DEFAULT_SELECTEDFILLCOLOR);
3709 	    filled = TRUE;
3710 	}
3711 	else if (GD_gui_state(sg) & GUI_STATE_DELETED) {
3712 	    pencolor = late_nnstring(sg, G_deletedpencolor, DEFAULT_DELETEDPENCOLOR);
3713 	    fillcolor = late_nnstring(sg, G_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR);
3714 	    filled = TRUE;
3715 	}
3716 	else if (GD_gui_state(sg) & GUI_STATE_VISITED) {
3717 	    pencolor = late_nnstring(sg, G_visitedpencolor, DEFAULT_VISITEDPENCOLOR);
3718 	    fillcolor = late_nnstring(sg, G_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR);
3719 	    filled = TRUE;
3720 	}
3721 	else {
3722 	    if (((color = agget(sg, "color")) != 0) && color[0])
3723 		fillcolor = pencolor = color;
3724 	    if (((color = agget(sg, "pencolor")) != 0) && color[0])
3725 		pencolor = color;
3726 	    if (((color = agget(sg, "fillcolor")) != 0) && color[0])
3727 		fillcolor = color;
3728 	    /* bgcolor is supported for backward compatibility
3729 	       if fill is set, fillcolor trumps bgcolor, so
3730                don't bother checking.
3731                if gradient is set fillcolor trumps bgcolor
3732              */
3733 	    if ((!filled || !fillcolor) && ((color = agget(sg, "bgcolor")) != 0) && color[0]) {
3734 		fillcolor = color;
3735 	        filled = FILL;
3736             }
3737 
3738 	}
3739 	if (!pencolor) pencolor = DEFAULT_COLOR;
3740 	if (!fillcolor) fillcolor = DEFAULT_FILL;
3741 	clrs[0] = NULL;
3742 	if (filled) {
3743 	    float frac;
3744 	    if (findStopColor (fillcolor, clrs, &frac)) {
3745         	gvrender_set_fillcolor(job, clrs[0]);
3746 		if (clrs[1])
3747 		    gvrender_set_gradient_vals(job,clrs[1],late_int(sg,G_gradientangle,0,0), frac);
3748 		else
3749 		    gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(sg,G_gradientangle,0,0), frac);
3750 		if (istyle & RADIAL)
3751 		    filled = RGRADIENT;
3752 	 	else
3753 		    filled = GRADIENT;
3754 	    }
3755 	    else
3756         	gvrender_set_fillcolor(job, fillcolor);
3757 	}
3758 
3759 	if (G_penwidth && ((s=ag_xget(sg,G_penwidth)) && s[0])) {
3760 	    penwidth = late_double(sg, G_penwidth, 1.0, 0.0);
3761             gvrender_set_penwidth(job, penwidth);
3762 	}
3763 
3764 	if (istyle & ROUNDED) {
3765 	    if ((doPerim = late_int(sg, G_peripheries, 1, 0)) || filled) {
3766 		AF[0] = GD_bb(sg).LL;
3767 		AF[2] = GD_bb(sg).UR;
3768 		AF[1].x = AF[2].x;
3769 		AF[1].y = AF[0].y;
3770 		AF[3].x = AF[0].x;
3771 		AF[3].y = AF[2].y;
3772 		if (doPerim)
3773     		    gvrender_set_pencolor(job, pencolor);
3774 		else
3775         	    gvrender_set_pencolor(job, "transparent");
3776 		round_corners(job, AF, 4, istyle, filled);
3777 	    }
3778 	}
3779 	else if (istyle & STRIPED) {
3780 	    int rv;
3781 	    AF[0] = GD_bb(sg).LL;
3782 	    AF[2] = GD_bb(sg).UR;
3783 	    AF[1].x = AF[2].x;
3784 	    AF[1].y = AF[0].y;
3785 	    AF[3].x = AF[0].x;
3786 	    AF[3].y = AF[2].y;
3787 	    if (late_int(sg, G_peripheries, 1, 0) == 0)
3788         	gvrender_set_pencolor(job, "transparent");
3789 	    else
3790     		gvrender_set_pencolor(job, pencolor);
3791 	    rv = stripedBox (job, AF, fillcolor, 0);
3792 	    if (rv > 1)
3793 		agerr (AGPREV, "in cluster %s\n", agnameof(sg));
3794 	    gvrender_box(job, GD_bb(sg), 0);
3795 	}
3796 	else {
3797 	    if (late_int(sg, G_peripheries, 1, 0)) {
3798     		gvrender_set_pencolor(job, pencolor);
3799 		gvrender_box(job, GD_bb(sg), filled);
3800 	    }
3801 	    else if (filled) {
3802         	gvrender_set_pencolor(job, "transparent");
3803 		gvrender_box(job, GD_bb(sg), filled);
3804 	    }
3805 	}
3806 
3807 	free (clrs[0]);
3808 	if ((lab = GD_label(sg)))
3809 	    emit_label(job, EMIT_CLABEL, lab);
3810 
3811 	if (doAnchor) {
3812 	    if (flags & EMIT_CLUSTERS_LAST) {
3813 		emit_map_rect(job, GD_bb(sg));
3814 		gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3815 	    }
3816 	    gvrender_end_anchor(job);
3817 	}
3818 
3819 	if (flags & EMIT_PREORDER) {
3820 	    for (n = agfstnode(sg); n; n = agnxtnode(sg, n)) {
3821 		emit_node(job, n);
3822 		for (e = agfstout(sg, n); e; e = agnxtout(sg, e))
3823 		    emit_edge(job, e);
3824 	    }
3825 	}
3826 	emit_end_cluster(job, g);
3827 	/* when drawing, lay down clusters before sub_clusters */
3828 	if (!(flags & EMIT_CLUSTERS_LAST))
3829 	    emit_clusters(job, sg, flags);
3830     }
3831 }
3832 
is_style_delim(int c)3833 static boolean is_style_delim(int c)
3834 {
3835     switch (c) {
3836     case '(':
3837     case ')':
3838     case ',':
3839     case '\0':
3840 	return TRUE;
3841     default:
3842 	return FALSE;
3843     }
3844 }
3845 
3846 #define SID 1
3847 
style_token(char ** s,agxbuf * xb)3848 static int style_token(char **s, agxbuf * xb)
3849 {
3850     char *p = *s;
3851     int token;
3852     char c;
3853 
3854     while (*p && (isspace(*p) || (*p == ',')))
3855 	p++;
3856     switch (*p) {
3857     case '\0':
3858 	token = 0;
3859 	break;
3860     case '(':
3861     case ')':
3862 	token = *p++;
3863 	break;
3864     default:
3865 	token = SID;
3866 	while (!is_style_delim(c = *p)) {
3867 	    agxbputc(xb, c);
3868 	    p++;
3869 	}
3870     }
3871     *s = p;
3872     return token;
3873 }
3874 
3875 #define FUNLIMIT 64
3876 static unsigned char outbuf[SMALLBUF];
3877 static agxbuf ps_xb;
3878 
3879 #if 0
3880 static void cleanup(void)
3881 {
3882     agxbfree(&ps_xb);
3883 }
3884 #endif
3885 
3886 /* parse_style:
3887  * This is one of the worst internal designs in graphviz.
3888  * The use of '\0' characters within strings seems cute but it
3889  * makes all of the standard functions useless if not dangerous.
3890  * Plus the function uses static memory for both the array and
3891  * the character buffer. One hopes all of the values are used
3892  * before the function is called again.
3893  */
parse_style(char * s)3894 char **parse_style(char *s)
3895 {
3896     static char *parse[FUNLIMIT];
3897     static boolean is_first = TRUE;
3898     int fun = 0;
3899     boolean in_parens = FALSE;
3900     unsigned char buf[SMALLBUF];
3901     char *p;
3902     int c;
3903     agxbuf xb;
3904 
3905     if (is_first) {
3906 	agxbinit(&ps_xb, SMALLBUF, outbuf);
3907 #if 0
3908 	atexit(cleanup);
3909 #endif
3910 	is_first = FALSE;
3911     }
3912 
3913     agxbinit(&xb, SMALLBUF, buf);
3914     p = s;
3915     while ((c = style_token(&p, &xb)) != 0) {
3916 	switch (c) {
3917 	case '(':
3918 	    if (in_parens) {
3919 		agerr(AGERR, "nesting not allowed in style: %s\n", s);
3920 		parse[0] = (char *) 0;
3921 		agxbfree(&xb);
3922 		return parse;
3923 	    }
3924 	    in_parens = TRUE;
3925 	    break;
3926 
3927 	case ')':
3928 	    if (in_parens == FALSE) {
3929 		agerr(AGERR, "unmatched ')' in style: %s\n", s);
3930 		parse[0] = (char *) 0;
3931 		agxbfree(&xb);
3932 		return parse;
3933 	    }
3934 	    in_parens = FALSE;
3935 	    break;
3936 
3937 	default:
3938 	    if (in_parens == FALSE) {
3939 		if (fun == FUNLIMIT - 1) {
3940 		    agerr(AGWARN, "truncating style '%s'\n", s);
3941 		    parse[fun] = (char *) 0;
3942 		    agxbfree(&xb);
3943 		    return parse;
3944 		}
3945 		agxbputc(&ps_xb, '\0');	/* terminate previous */
3946 		parse[fun++] = agxbnext(&ps_xb);
3947 	    }
3948 	    agxbput(&ps_xb, agxbuse(&xb));
3949 	    agxbputc(&ps_xb, '\0');
3950 	}
3951     }
3952 
3953     if (in_parens) {
3954 	agerr(AGERR, "unmatched '(' in style: %s\n", s);
3955 	parse[0] = (char *) 0;
3956 	agxbfree(&xb);
3957 	return parse;
3958     }
3959     parse[fun] = (char *) 0;
3960     agxbfree(&xb);
3961     (void)agxbuse(&ps_xb);		/* adds final '\0' to buffer */
3962     return parse;
3963 }
3964 
bezier_bb(bezier bz)3965 static boxf bezier_bb(bezier bz)
3966 {
3967     int i;
3968     pointf p, p1, p2;
3969     boxf bb;
3970 
3971     assert(bz.size > 0);
3972     assert(bz.size % 3 == 1);
3973     bb.LL = bb.UR = bz.list[0];
3974     for (i = 1; i < bz.size;) {
3975 	/* take mid-point between two control points for bb calculation */
3976 	p1=bz.list[i];
3977 	i++;
3978 	p2=bz.list[i];
3979 	i++;
3980 	p.x = ( p1.x + p2.x ) / 2;
3981 	p.y = ( p1.y + p2.y ) / 2;
3982         EXPANDBP(bb,p);
3983 
3984 	p=bz.list[i];
3985         EXPANDBP(bb,p);
3986 	i++;
3987     }
3988     return bb;
3989 }
3990 
init_splines_bb(splines * spl)3991 static void init_splines_bb(splines *spl)
3992 {
3993     int i;
3994     bezier bz;
3995     boxf bb, b;
3996 
3997     assert(spl->size > 0);
3998     bz = spl->list[0];
3999     bb = bezier_bb(bz);
4000     for (i = 0; i < spl->size; i++) {
4001         if (i > 0) {
4002             bz = spl->list[i];
4003             b = bezier_bb(bz);
4004             EXPANDBB(bb, b);
4005         }
4006         if (bz.sflag) {
4007             b = arrow_bb(bz.sp, bz.list[0], 1, bz.sflag);
4008             EXPANDBB(bb, b);
4009         }
4010         if (bz.eflag) {
4011             b = arrow_bb(bz.ep, bz.list[bz.size - 1], 1, bz.eflag);
4012             EXPANDBB(bb, b);
4013         }
4014     }
4015     spl->bb = bb;
4016 }
4017 
init_bb_edge(edge_t * e)4018 static void init_bb_edge(edge_t *e)
4019 {
4020     splines *spl;
4021 
4022     spl = ED_spl(e);
4023     if (spl)
4024         init_splines_bb(spl);
4025 
4026 //    lp = ED_label(e);
4027 //    if (lp)
4028 //        {}
4029 }
4030 
init_bb_node(graph_t * g,node_t * n)4031 static void init_bb_node(graph_t *g, node_t *n)
4032 {
4033     edge_t *e;
4034 
4035     ND_bb(n).LL.x = ND_coord(n).x - ND_lw(n);
4036     ND_bb(n).LL.y = ND_coord(n).y - ND_ht(n) / 2.;
4037     ND_bb(n).UR.x = ND_coord(n).x + ND_rw(n);
4038     ND_bb(n).UR.y = ND_coord(n).y + ND_ht(n) / 2.;
4039 
4040     for (e = agfstout(g, n); e; e = agnxtout(g, e))
4041         init_bb_edge(e);
4042 
4043     /* IDEA - could also save in the node the bb of the node and
4044     all of its outedges, then the scan time would be proportional
4045     to just the number of nodes for many graphs.
4046     Wouldn't work so well if the edges are sprawling all over the place
4047     because then the boxes would overlap a lot and require more tests,
4048     but perhaps that wouldn't add much to the cost before trying individual
4049     nodes and edges. */
4050 }
4051 
init_bb(graph_t * g)4052 static void init_bb(graph_t *g)
4053 {
4054     node_t *n;
4055 
4056     for (n = agfstnode(g); n; n = agnxtnode(g, n))
4057         init_bb_node(g, n);
4058 }
4059 
4060 extern gvevent_key_binding_t gvevent_key_binding[];
4061 extern int gvevent_key_binding_size;
4062 extern gvdevice_callbacks_t gvdevice_callbacks;
4063 
4064 /* gv_fixLocale:
4065  * Set LC_NUMERIC to "C" to get expected interpretation of %f
4066  * in printf functions. Languages like postscript and dot expect
4067  * floating point numbers to use a decimal point.
4068  *
4069  * If set is non-zero, the "C" locale set;
4070  * if set is zero, the original locale is reset.
4071  * Calls to the function can nest.
4072  */
gv_fixLocale(int set)4073 void gv_fixLocale (int set)
4074 {
4075     static char* save_locale;
4076     static int cnt;
4077 
4078     if (set) {
4079 	cnt++;
4080 	if (cnt == 1) {
4081 	    save_locale = strdup (setlocale (LC_NUMERIC, NULL));
4082 	    setlocale (LC_NUMERIC, "C");
4083 	}
4084     }
4085     else if (cnt > 0) {
4086 	cnt--;
4087 	if (cnt == 0) {
4088 	    setlocale (LC_NUMERIC, save_locale);
4089 	    free (save_locale);
4090 	}
4091     }
4092 }
4093 
4094 
4095 #define FINISH() if (Verbose) fprintf(stderr,"gvRenderJobs %s: %.2f secs.\n", agnameof(g), elapsed_sec())
4096 
gvRenderJobs(GVC_t * gvc,graph_t * g)4097 int gvRenderJobs (GVC_t * gvc, graph_t * g)
4098 {
4099     static GVJ_t *prevjob;
4100     GVJ_t *job, *firstjob;
4101 
4102     if (Verbose)
4103 	start_timer();
4104 
4105     if (!LAYOUT_DONE(g)) {
4106         agerr (AGERR, "Layout was not done.  Missing layout plugins? \n");
4107 	FINISH();
4108         return -1;
4109     }
4110 
4111     init_bb(g);
4112     init_gvc(gvc, g);
4113     init_layering(gvc, g);
4114 
4115     gv_fixLocale (1);
4116     for (job = gvjobs_first(gvc); job; job = gvjobs_next(gvc)) {
4117 	if (gvc->gvg) {
4118 	    job->input_filename = gvc->gvg->input_filename;
4119 	    job->graph_index = gvc->gvg->graph_index;
4120 	}
4121 	else {
4122 	    job->input_filename = NULL;
4123 	    job->graph_index = 0;
4124 	}
4125 	job->common = &(gvc->common);
4126 	job->layout_type = gvc->layout.type;
4127 	job->keybindings = gvevent_key_binding;
4128 	job->numkeys = gvevent_key_binding_size;
4129 	if (!GD_drawing(g)) {
4130 	    agerr (AGERR, "layout was not done\n");
4131 	    gv_fixLocale (0);
4132 	    FINISH();
4133 	    return -1;
4134 	}
4135 
4136         job->output_lang = gvrender_select(job, job->output_langname);
4137         if (job->output_lang == NO_SUPPORT) {
4138             agerr (AGERR, "renderer for %s is unavailable\n", job->output_langname);
4139 	    gv_fixLocale (0);
4140 	    FINISH();
4141             return -1;
4142         }
4143 
4144         switch (job->output_lang) {
4145         case VTX:
4146             /* output sorted, i.e. all nodes then all edges */
4147             job->flags |= EMIT_SORTED;
4148             break;
4149         case DIA:
4150             /* output in preorder traversal of the graph */
4151             job->flags |= EMIT_PREORDER
4152 		       | GVDEVICE_BINARY_FORMAT;
4153             break;
4154         default:
4155             job->flags |= chkOrder(g);
4156             break;
4157         }
4158 
4159 	/* 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 */
4160         firstjob = gvc->active_jobs;
4161         if (firstjob) {
4162 	    if (! (firstjob->flags & GVDEVICE_DOES_PAGES)
4163 	      || (strcmp(job->output_langname,firstjob->output_langname))) {
4164 
4165 	        gvrender_end_job(firstjob);
4166 
4167             	gvc->active_jobs = NULL; /* clear active list */
4168 	    	gvc->common.viewNum = 0;
4169 	    	prevjob = NULL;
4170             }
4171         }
4172         else {
4173 	    prevjob = NULL;
4174         }
4175 
4176 	if (prevjob) {
4177             prevjob->next_active = job;  /* insert job in active list */
4178 	    job->output_file = prevjob->output_file;  /* FIXME - this is dumb ! */
4179 	}
4180 	else {
4181 	    if (gvrender_begin_job(job))
4182 		continue;
4183 	    gvc->active_jobs = job;   /* first job of new list */
4184 	}
4185 	job->next_active = NULL;      /* terminate active list */
4186 	job->callbacks = &gvdevice_callbacks;
4187 
4188 	init_job_pad(job);
4189 	init_job_margin(job);
4190 	init_job_dpi(job, g);
4191 	init_job_viewport(job, g);
4192 	init_job_pagination(job, g);
4193 
4194 	if (! (job->flags & GVDEVICE_EVENTS)) {
4195 #ifdef DEBUG
4196     		/* Show_boxes is not defined, if at all,
4197                  * until splines are generated in dot
4198                  */
4199 	    job->common->show_boxes = (const char**)Show_boxes;
4200 #endif
4201 	    emit_graph(job, g);
4202 	}
4203 
4204         /* the last job, after all input graphs are processed,
4205          *      is finalized from gvFinalize()
4206          */
4207 	prevjob = job;
4208     }
4209     gv_fixLocale (0);
4210     FINISH();
4211     return 0;
4212 }
4213 
4214 /* findStopColor:
4215  * Check for colon in colorlist. If one exists, and not the first
4216  * character, store the characters before the colon in clrs[0] and
4217  * the characters after the colon (and before the next or end-of-string)
4218  * in clrs[1]. If there are no characters after the first colon, clrs[1]
4219  * is NULL. Return TRUE.
4220  * If there is no non-trivial string before a first colon, set clrs[0] to
4221  * NULL and return FALSE.
4222  *
4223  * Note that memory is allocated as a single block stored in clrs[0] and
4224  * must be freed by calling function.
4225  */
findStopColor(char * colorlist,char * clrs[2],float * frac)4226 boolean findStopColor (char* colorlist, char* clrs[2], float* frac)
4227 {
4228     colorsegs_t* segs = NULL;
4229     int rv;
4230 
4231     rv = parseSegs (colorlist, 0, &segs);
4232     if (rv || (segs->numc < 2) || (segs->segs[0].color == NULL)) {
4233 	clrs[0] = NULL;
4234 	if (segs) freeSegs (segs);
4235 	return FALSE;
4236     }
4237 
4238     if (segs->numc > 2)
4239 	agerr (AGWARN, "More than 2 colors specified for a gradient - ignoring remaining\n");
4240 
4241     clrs[0] = N_GNEW (strlen(colorlist)+1,char);
4242     strcpy (clrs[0], segs->segs[0].color);
4243     if (segs->segs[1].color) {
4244 	clrs[1] = clrs[0] + (strlen(clrs[0])+1);
4245 	strcpy (clrs[1], segs->segs[1].color);
4246     }
4247     else
4248 	clrs[1] = NULL;
4249 
4250     if (segs->segs[0].hasFraction)
4251 	*frac = segs->segs[0].t;
4252     else if (segs->segs[1].hasFraction)
4253 	*frac = 1 - segs->segs[1].t;
4254     else
4255 	*frac = 0;
4256 
4257     freeSegs (segs);
4258     return TRUE;
4259 }
4260 
4261