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