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