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
38typedef struct {
39 xdot_op op;
40 boxf bb;
41 textspan_t* span;
42} exdot_op;
43
44void* 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
81static char *defaultlinestyle[3] = { "solid\0", "setlinewidth\0001\0", 0 };
82
83/* push empty graphic state for current object */
84obj_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 */
113void 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 */
145int
146initMapData (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
180static void
181layerPagePrefix (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 */
197char*
198getObjId (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
253static char*
254interpretCRNL (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 */
297static char*
298preprocessTooltip(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
315static void
316initObjMapData (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
341static 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
366static 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
410typedef 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 */
416typedef 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
422static void
423freeSegs (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 */
436static 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 */
476static int
477parseSegs (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 */
574int
575wedgedEllipse (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 */
625int
626stripedBox (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
672void 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
698static 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 */
728static 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 */
738static 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 */
760static 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 */
784static 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 */
795void 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)
833static 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
843typedef 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
852static 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 */
864static 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 */
894static 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 */
915static 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 */
934static 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 */
979static 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
1022static 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
1032static 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
1048static 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
1093static 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 */
1114static 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 */
1143static 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 */
1186static 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
1199static 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 */
1230static 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
1240static 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
1266static boolean validlayer(GVJ_t *job)
1267{
1268 return (job->layerNum <= job->numLayers);
1269}
1270
1271static 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
1282static 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
1305static 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
1427static void firstpage(GVJ_t *job)
1428{
1429 job->pagesArrayElem = job->pagesArrayFirst;
1430}
1431
1432static 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
1440static 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
1452static 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
1465static 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
1480static pointf*
1481copyPts (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
1499static 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
1614static 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
1668static 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
1721fprintf(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
1739static 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
1761static 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
1781static 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
1799static boolean node_in_box(node_t *n, boxf b)
1800{
1801 return boxf_overlap(ND_bb(n), b);
1802}
1803
1804static 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
1950static void emit_end_node(GVJ_t * job)
1951{
1952 gvrender_end_node(job);
1953 pop_obj_state(job);
1954}
1955
1956/* emit_node:
1957 */
1958static 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 */
1997static 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 */
2010static 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
2031static 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. */
2059static 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 */
2086static 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 */
2103static 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 */
2159static 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
2236static void free_stroke (stroke_t* sp)
2237{
2238 if (sp) {
2239 free (sp->vertices);
2240 free (sp);
2241 }
2242}
2243
2244typedef double (*radfunc_t)(double,double,double);
2245
2246static double forfunc (double curlen, double totallen, double initwid)
2247{
2248 return ((1 - (curlen/totallen))*initwid/2.0);
2249}
2250
2251static double revfunc (double curlen, double totallen, double initwid)
2252{
2253 return (((curlen/totallen))*initwid/2.0);
2254}
2255
2256static double nonefunc (double curlen, double totallen, double initwid)
2257{
2258 return (initwid/2.0);
2259}
2260
2261static 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
2268static radfunc_t
2269taperfun (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
2281static 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
2506static 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
2526static 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
2710static void
2711emit_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 */
2771static 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
2823static 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
2892static 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
2936static char adjust[] = {'l', 'n', 'r'};
2937
2938static void
2939expandBB (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
2951static boxf
2952ptsBB (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
2976static boxf
2977textBB (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
3001static void
3002freePara (exdot_op* op)
3003{
3004 if (op->op.kind == xd_text)
3005 free_textspan (op->span, 1);
3006}
3007
3008boxf 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
3087static 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
3156static 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
3175static 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
3199static 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
3222static 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
3304fprintf(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
3311static 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
3333static 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
3388static 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
3451static 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
3465static 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
3473static 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
3548void 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
3574fprintf(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 */
3606static void free_string_entry(Dict_t * dict, char *key, Dtdisc_t * disc)
3607{
3608 free(key);
3609}
3610
3611static Dict_t *strings;
3612static 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
3624int 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
3635void emit_once_reset(void)
3636{
3637 if (strings) {
3638 dtclose(strings);
3639 strings = 0;
3640 }
3641}
3642
3643static 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
3657static void emit_end_cluster(GVJ_t * job, Agraph_t * g)
3658{
3659 gvrender_end_cluster(job, g);
3660 pop_obj_state(job);
3661}
3662
3663void 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
3833static 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
3848static 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
3876static unsigned char outbuf[SMALLBUF];
3877static agxbuf ps_xb;
3878
3879#if 0
3880static 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 */
3894char **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
3965static 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
3991static 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
4018static 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
4031static 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
4052static 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
4060extern gvevent_key_binding_t gvevent_key_binding[];
4061extern int gvevent_key_binding_size;
4062extern 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 */
4073void 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
4097int 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 */
4226boolean 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