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