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