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