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