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