Graphviz 13.0.0~dev.20250210.0415
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 <common/htmltable.h>
29#include <gvc/gvc.h>
30#include <cdt/cdt.h>
31#include <pathplan/pathgeom.h>
32#include <util/agxbuf.h>
33#include <util/alloc.h>
34#include <util/gv_ctype.h>
35#include <util/gv_math.h>
36#include <util/list.h>
37#include <util/streq.h>
38#include <util/strview.h>
39#include <util/tokenize.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 SWAP(&n0, &n1);
1014 }
1015 rval = BETWEEN(n0, layerNum, n1);
1016 }
1017 } else if (w0 != NULL) {
1018 n0 = layer_index(gvc, w0, layerNum);
1019 rval = (n0 == layerNum);
1020 } else {
1021 rval = false;
1022 }
1023 part_in_p = NULL;
1024 }
1025 free(spec_copy);
1026 return rval;
1027}
1028
1029static bool selectedlayer(GVJ_t *job, char *spec)
1030{
1031 return selectedLayer (job->gvc, job->layerNum, job->numLayers, spec);
1032}
1033
1034/* Parse the graph's layerselect attribute, which determines
1035 * which layers are emitted. The specification is the same used
1036 * by the layer attribute.
1037 *
1038 * If we find n layers, we return an array arr of n+2 ints. arr[0]=n.
1039 * arr[n+1]=numLayers+1, acting as a sentinel. The other entries give
1040 * the desired layer indices.
1041 *
1042 * If no layers are detected, NULL is returned.
1043 *
1044 * This implementation does a linear walk through each layer index and
1045 * uses selectedLayer to match it against p. There is probably a more
1046 * efficient way to do this, but this is simple and until we find people
1047 * using huge numbers of layers, it should be adequate.
1048 */
1049static int *parse_layerselect(GVC_t *gvc, char *p) {
1050 int* laylist = gv_calloc(gvc->numLayers + 2, sizeof(int));
1051 int i, cnt = 0;
1052 for (i = 1; i <=gvc->numLayers; i++) {
1053 if (selectedLayer (gvc, i, gvc->numLayers, p)) {
1054 laylist[++cnt] = i;
1055 }
1056 }
1057 if (cnt) {
1058 laylist[0] = cnt;
1059 laylist[cnt+1] = gvc->numLayers+1;
1060 }
1061 else {
1062 agwarningf("The layerselect attribute \"%s\" does not match any layer specifed by the layers attribute - ignored.\n", p);
1063 free (laylist);
1064 laylist = NULL;
1065 }
1066 return laylist;
1067}
1068
1069DEFINE_LIST(layer_names, char*)
1070
1071/* Split input string into tokens, with separators specified by
1072 * the layersep attribute. Store the values in the gvc->layerIDs array,
1073 * starting at index 1, and return the count.
1074 * Note that there is no mechanism
1075 * to free the memory before exit.
1076 */
1077static int parse_layers(GVC_t *gvc, graph_t * g, char *p)
1078{
1079 char *tok;
1080
1081 gvc->layerDelims = agget(g, "layersep");
1082 if (!gvc->layerDelims)
1084 gvc->layerListDelims = agget(g, "layerlistsep");
1085 if (!gvc->layerListDelims)
1087 if ((tok = strpbrk (gvc->layerDelims, gvc->layerListDelims))) { /* conflict in delimiter strings */
1088 agwarningf("The character \'%c\' appears in both the layersep and layerlistsep attributes - layerlistsep ignored.\n", *tok);
1089 gvc->layerListDelims = "";
1090 }
1091
1092 gvc->layers = gv_strdup(p);
1093 layer_names_t layerIDs = {0};
1094
1095 // inferred entry for the first (unnamed) layer
1096 layer_names_append(&layerIDs, NULL);
1097
1098 for (tok = strtok(gvc->layers, gvc->layerDelims); tok;
1099 tok = strtok(NULL, gvc->layerDelims)) {
1100 layer_names_append(&layerIDs, tok);
1101 }
1102
1103 assert(layer_names_size(&layerIDs) - 1 <= INT_MAX);
1104 int ntok = (int)(layer_names_size(&layerIDs) - 1);
1105
1106 // if we found layers, save them for later reference
1107 if (layer_names_size(&layerIDs) > 1) {
1108 layer_names_append(&layerIDs, NULL); // add a terminating entry
1109 gvc->layerIDs = layer_names_detach(&layerIDs);
1110 }
1111 layer_names_free(&layerIDs);
1112
1113 return ntok;
1114}
1115
1116/* Determine order of output.
1117 * Output usually in breadth first graph walk order
1118 */
1119static int chkOrder(graph_t * g)
1120{
1121 char *p = agget(g, "outputorder");
1122 if (p) {
1123 if (!strcmp(p, "nodesfirst"))
1124 return EMIT_SORTED;
1125 if (!strcmp(p, "edgesfirst"))
1126 return EMIT_EDGE_SORTED;
1127 }
1128 return 0;
1129}
1130
1131static void init_layering(GVC_t * gvc, graph_t * g)
1132{
1133 char *str;
1134
1135 /* free layer strings and pointers from previous graph */
1136 free(gvc->layers);
1137 gvc->layers = NULL;
1138 free(gvc->layerIDs);
1139 gvc->layerIDs = NULL;
1140 free(gvc->layerlist);
1141 gvc->layerlist = NULL;
1142 if ((str = agget(g, "layers")) != 0) {
1144 if ((str = agget(g, "layerselect")) != 0 && *str) {
1146 }
1147 } else {
1148 gvc->numLayers = 1;
1149 }
1150}
1151
1153static int numPhysicalLayers (GVJ_t *job)
1154{
1155 if (job->gvc->layerlist) {
1156 return job->gvc->layerlist[0];
1157 }
1158 else
1159 return job->numLayers;
1160
1161}
1162
1163static void firstlayer(GVJ_t *job, int** listp)
1164{
1165 job->numLayers = job->gvc->numLayers;
1166 if (job->gvc->layerlist) {
1167 int *list = job->gvc->layerlist;
1168 int cnt = *list++;
1169 if (cnt > 1 && !(job->flags & GVDEVICE_DOES_LAYERS)) {
1170 agwarningf("layers not supported in %s output\n",
1171 job->output_langname);
1172 list[1] = job->numLayers + 1; /* only one layer printed */
1173 }
1174 job->layerNum = *list++;
1175 *listp = list;
1176 }
1177 else {
1178 if (job->numLayers > 1 && !(job->flags & GVDEVICE_DOES_LAYERS)) {
1179 agwarningf("layers not supported in %s output\n",
1180 job->output_langname);
1181 job->numLayers = 1;
1182 }
1183 job->layerNum = 1;
1184 *listp = NULL;
1185 }
1186}
1187
1188static bool validlayer(GVJ_t *job)
1189{
1190 return job->layerNum <= job->numLayers;
1191}
1192
1193static void nextlayer(GVJ_t *job, int** listp)
1194{
1195 int *list = *listp;
1196 if (list) {
1197 job->layerNum = *list++;
1198 *listp = list;
1199 }
1200 else
1201 job->layerNum++;
1202}
1203
1204static point pagecode(GVJ_t *job, char c)
1205{
1206 point rv = {0};
1207 switch (c) {
1208 case 'T':
1209 job->pagesArrayFirst.y = job->pagesArraySize.y - 1;
1210 rv.y = -1;
1211 break;
1212 case 'B':
1213 rv.y = 1;
1214 break;
1215 case 'L':
1216 rv.x = 1;
1217 break;
1218 case 'R':
1219 job->pagesArrayFirst.x = job->pagesArraySize.x - 1;
1220 rv.x = -1;
1221 break;
1222 default:
1223 // ignore; will trigger a warning later in our caller
1224 break;
1225 }
1226 return rv;
1227}
1228
1229static void init_job_pagination(GVJ_t * job, graph_t *g)
1230{
1231 GVC_t *gvc = job->gvc;
1232 pointf pageSize; /* page size for the graph - points*/
1233 pointf centering = {0}; // centering offset - points
1234
1235 /* unpaginated image size - in points - in graph orientation */
1236 pointf imageSize = job->view; // image size on one page of the graph - points
1237
1238 /* rotate imageSize to page orientation */
1239 if (job->rotation)
1240 imageSize = exch_xyf(imageSize);
1241
1242 /* margin - in points - in page orientation */
1243 pointf margin = job->margin; // margin for a page of the graph - points
1244
1245 /* determine pagination */
1247 /* page was set by user */
1248
1249 /* determine size of page for image */
1250 pageSize.x = gvc->pageSize.x - 2 * margin.x;
1251 pageSize.y = gvc->pageSize.y - 2 * margin.y;
1252
1253 if (pageSize.x < EPSILON)
1254 job->pagesArraySize.x = 1;
1255 else {
1256 job->pagesArraySize.x = (int)(imageSize.x / pageSize.x);
1257 if (imageSize.x - job->pagesArraySize.x * pageSize.x > EPSILON)
1258 job->pagesArraySize.x++;
1259 }
1260 if (pageSize.y < EPSILON)
1261 job->pagesArraySize.y = 1;
1262 else {
1263 job->pagesArraySize.y = (int)(imageSize.y / pageSize.y);
1264 if (imageSize.y - job->pagesArraySize.y * pageSize.y > EPSILON)
1265 job->pagesArraySize.y++;
1266 }
1267 job->numPages = job->pagesArraySize.x * job->pagesArraySize.y;
1268
1269 /* find the drawable size in points */
1270 imageSize.x = fmin(imageSize.x, pageSize.x);
1271 imageSize.y = fmin(imageSize.y, pageSize.y);
1272 } else {
1273 /* page not set by user, use default from renderer */
1274 if (job->render.features) {
1275 pageSize.x = job->device.features->default_pagesize.x - 2*margin.x;
1276 pageSize.x = fmax(pageSize.x, 0);
1277 pageSize.y = job->device.features->default_pagesize.y - 2*margin.y;
1278 pageSize.y = fmax(pageSize.y, 0);
1279 }
1280 else
1281 pageSize.x = pageSize.y = 0.;
1282 job->pagesArraySize.x = job->pagesArraySize.y = job->numPages = 1;
1283
1284 pageSize.x = fmax(pageSize.x, imageSize.x);
1285 pageSize.y = fmax(pageSize.y, imageSize.y);
1286 }
1287
1288 /* initial window size */
1289 job->width = ROUND((pageSize.x + 2*margin.x) * job->dpi.x / POINTS_PER_INCH);
1290 job->height = ROUND((pageSize.y + 2*margin.y) * job->dpi.y / POINTS_PER_INCH);
1291
1292 /* set up pagedir */
1293 job->pagesArrayMajor = (point){0};
1294 job->pagesArrayMinor = (point){0};
1295 job->pagesArrayFirst = (point){0};
1296 job->pagesArrayMajor = pagecode(job, gvc->pagedir[0]);
1297 job->pagesArrayMinor = pagecode(job, gvc->pagedir[1]);
1298 if (abs(job->pagesArrayMajor.x + job->pagesArrayMinor.x) != 1
1299 || abs(job->pagesArrayMajor.y + job->pagesArrayMinor.y) != 1) {
1300 job->pagesArrayMajor = pagecode(job, 'B');
1301 job->pagesArrayMinor = pagecode(job, 'L');
1302 agwarningf("pagedir=%s ignored\n", gvc->pagedir);
1303 }
1304
1305 /* determine page box including centering */
1306 if (GD_drawing(g)->centered) {
1307 if (pageSize.x > imageSize.x)
1308 centering.x = (pageSize.x - imageSize.x) / 2;
1309 if (pageSize.y > imageSize.y)
1310 centering.y = (pageSize.y - imageSize.y) / 2;
1311 }
1312
1313 /* rotate back into graph orientation */
1314 if (job->rotation) {
1315 imageSize = exch_xyf(imageSize);
1316 pageSize = exch_xyf(pageSize);
1317 margin = exch_xyf(margin);
1318 centering = exch_xyf(centering);
1319 }
1320
1321 /* canvas area, centered if necessary */
1322 job->canvasBox.LL.x = margin.x + centering.x;
1323 job->canvasBox.LL.y = margin.y + centering.y;
1324 job->canvasBox.UR.x = margin.x + centering.x + imageSize.x;
1325 job->canvasBox.UR.y = margin.y + centering.y + imageSize.y;
1326
1327 /* size of one page in graph units */
1328 job->pageSize.x = imageSize.x / job->zoom;
1329 job->pageSize.y = imageSize.y / job->zoom;
1330
1331 /* pageBoundingBox in device units and page orientation */
1332 job->pageBoundingBox.LL.x = ROUND(job->canvasBox.LL.x * job->dpi.x / POINTS_PER_INCH);
1333 job->pageBoundingBox.LL.y = ROUND(job->canvasBox.LL.y * job->dpi.y / POINTS_PER_INCH);
1334 job->pageBoundingBox.UR.x = ROUND(job->canvasBox.UR.x * job->dpi.x / POINTS_PER_INCH);
1335 job->pageBoundingBox.UR.y = ROUND(job->canvasBox.UR.y * job->dpi.y / POINTS_PER_INCH);
1336 if (job->rotation) {
1339 job->canvasBox.LL = exch_xyf(job->canvasBox.LL);
1340 job->canvasBox.UR = exch_xyf(job->canvasBox.UR);
1341 }
1342}
1343
1344static void firstpage(GVJ_t *job)
1345{
1346 job->pagesArrayElem = job->pagesArrayFirst;
1347}
1348
1349static bool validpage(GVJ_t *job)
1350{
1351 return job->pagesArrayElem.x >= 0
1352 && job->pagesArrayElem.x < job->pagesArraySize.x
1353 && job->pagesArrayElem.y >= 0
1354 && job->pagesArrayElem.y < job->pagesArraySize.y;
1355}
1356
1357static void nextpage(GVJ_t *job)
1358{
1360 if (!validpage(job)) {
1361 if (job->pagesArrayMajor.y)
1362 job->pagesArrayElem.x = job->pagesArrayFirst.x;
1363 else
1364 job->pagesArrayElem.y = job->pagesArrayFirst.y;
1366 }
1367}
1368
1369static bool write_edge_test(Agraph_t * g, Agedge_t * e)
1370{
1371 Agraph_t *sg;
1372 int c;
1373
1374 for (c = 1; c <= GD_n_cluster(g); c++) {
1375 sg = GD_clust(g)[c];
1376 if (agcontains(sg, e))
1377 return false;
1378 }
1379 return true;
1380}
1381
1382static bool write_node_test(Agraph_t * g, Agnode_t * n)
1383{
1384 Agraph_t *sg;
1385 int c;
1386
1387 for (c = 1; c <= GD_n_cluster(g); c++) {
1388 sg = GD_clust(g)[c];
1389 if (agcontains(sg, n))
1390 return false;
1391 }
1392 return true;
1393}
1394
1395static pointf *copyPts(xdot_point *inpts, size_t numpts) {
1396 pointf *pts = gv_calloc(numpts, sizeof(pointf));
1397 for (size_t i = 0; i < numpts; i++) {
1398 pts[i].x = inpts[i].x;
1399 pts[i].y = inpts[i].y;
1400 }
1401 return pts;
1402}
1403
1404static void emit_xdot (GVJ_t * job, xdot* xd)
1405{
1406 int image_warn = 1;
1407 exdot_op* op;
1408 int angle;
1409 char** styles = NULL;
1410 int filled = FILL;
1411
1412 op = (exdot_op*)(xd->ops);
1413 for (size_t i = 0; i < xd->cnt; i++) {
1414 switch (op->op.kind) {
1415 case xd_filled_ellipse :
1416 case xd_unfilled_ellipse :
1417 if (boxf_overlap(op->bb, job->clip)) {
1418 pointf pts[] = {{.x = op->op.u.ellipse.x - op->op.u.ellipse.w,
1419 .y = op->op.u.ellipse.y - op->op.u.ellipse.h},
1420 {.x = op->op.u.ellipse.x + op->op.u.ellipse.w,
1421 .y = op->op.u.ellipse.y + op->op.u.ellipse.h}};
1422 gvrender_ellipse(job, pts, op->op.kind == xd_filled_ellipse ? filled : 0);
1423 }
1424 break;
1425 case xd_filled_polygon :
1426 case xd_unfilled_polygon :
1427 if (boxf_overlap(op->bb, job->clip)) {
1428 pointf *pts = copyPts(op->op.u.polygon.pts, op->op.u.polygon.cnt);
1429 assert(op->op.u.polygon.cnt <= INT_MAX &&
1430 "polygon count exceeds gvrender_polygon support");
1431 gvrender_polygon(job, pts, op->op.u.polygon.cnt,
1432 op->op.kind == xd_filled_polygon ? filled : 0);
1433 free(pts);
1434 }
1435 break;
1436 case xd_filled_bezier :
1437 case xd_unfilled_bezier :
1438 if (boxf_overlap(op->bb, job->clip)) {
1439 pointf *pts = copyPts(op->op.u.bezier.pts, op->op.u.bezier.cnt);
1440 gvrender_beziercurve(job, pts, op->op.u.bezier.cnt,
1441 op->op.kind == xd_filled_bezier ? filled : 0);
1442 free(pts);
1443 }
1444 break;
1445 case xd_polyline :
1446 if (boxf_overlap(op->bb, job->clip)) {
1447 pointf *pts = copyPts(op->op.u.polyline.pts, op->op.u.polyline.cnt);
1448 gvrender_polyline(job, pts, op->op.u.polyline.cnt);
1449 free(pts);
1450 }
1451 break;
1452 case xd_text :
1453 if (boxf_overlap(op->bb, job->clip)) {
1454 pointf pt = {.x = op->op.u.text.x, .y = op->op.u.text.y};
1455 gvrender_textspan(job, pt, op->span);
1456 }
1457 break;
1458 case xd_fill_color :
1459 gvrender_set_fillcolor(job, op->op.u.color);
1460 filled = FILL;
1461 break;
1462 case xd_pen_color :
1463 gvrender_set_pencolor(job, op->op.u.color);
1464 filled = FILL;
1465 break;
1466 case xd_grad_fill_color :
1467 {
1468 char* clr0;
1469 char* clr1;
1470 float frac;
1471 if (op->op.u.grad_color.type == xd_radial) {
1473 clr0 = p->stops[0].color;
1474 clr1 = p->stops[1].color;
1475 frac = p->stops[1].frac;
1476 if (p->x1 == p->x0 && p->y1 == p->y0)
1477 angle = 0;
1478 else
1479 angle = (int)(180.0*acos((p->x0 - p->x1)/p->r0)/M_PI);
1480 gvrender_set_fillcolor(job, clr0);
1481 gvrender_set_gradient_vals(job, clr1, angle, frac);
1482 filled = RGRADIENT;
1483 }
1484 else {
1486 clr0 = p->stops[0].color;
1487 clr1 = p->stops[1].color;
1488 frac = p->stops[1].frac;
1489 angle = (int)(180.0*atan2(p->y1-p->y0,p->x1-p->x0)/M_PI);
1490 gvrender_set_fillcolor(job, clr0);
1491 gvrender_set_gradient_vals(job, clr1, angle, frac);
1492 filled = GRADIENT;
1493 }
1494 }
1495 break;
1496 case xd_grad_pen_color :
1497 agwarningf("gradient pen colors not yet supported.\n");
1498 break;
1499 case xd_font :
1500 /* fontsize and fontname already encoded via xdotBB */
1501 break;
1502 case xd_style :
1503 styles = parse_style (op->op.u.style);
1504 gvrender_set_style (job, styles);
1505 break;
1506 case xd_fontchar :
1507 /* font characteristics already encoded via xdotBB */
1508 break;
1509 case xd_image :
1510 if (image_warn) {
1511 agwarningf("Images unsupported in \"background\" attribute\n");
1512 image_warn = 0;
1513 }
1514 break;
1515 default:
1516 UNREACHABLE();
1517 }
1518 op++;
1519 }
1520 if (styles)
1522}
1523
1524static void emit_background(GVJ_t * job, graph_t *g)
1525{
1526 xdot* xd;
1527 char *str;
1528 int dfltColor;
1529
1530 /* if no bgcolor specified - first assume default of "white" */
1531 if (! ((str = agget(g, "bgcolor")) && str[0])) {
1532 str = "white";
1533 dfltColor = 1;
1534 }
1535 else
1536 dfltColor = 0;
1537
1538
1539 /* if device has no truecolor support, change "transparent" to "white" */
1540 if (! (job->flags & GVDEVICE_DOES_TRUECOLOR) && (streq(str, "transparent"))) {
1541 str = "white";
1542 dfltColor = 1;
1543 }
1544
1545 /* except for "transparent" on truecolor, or default "white" on (assumed) white paper, paint background */
1546 if (!( ((job->flags & GVDEVICE_DOES_TRUECOLOR) && streq(str, "transparent"))
1547 || ((job->flags & GVRENDER_NO_WHITE_BG) && dfltColor))) {
1548 char *clrs[2] = {0};
1549 double frac;
1550
1551 if ((findStopColor (str, clrs, &frac))) {
1552 int filled;
1553 graphviz_polygon_style_t istyle = {0};
1554 gvrender_set_fillcolor(job, clrs[0]);
1555 gvrender_set_pencolor(job, "transparent");
1556 checkClusterStyle(g, &istyle);
1557 if (clrs[1])
1558 gvrender_set_gradient_vals(job,clrs[1],late_int(g,G_gradientangle,0,0), frac);
1559 else
1561 if (istyle.radial)
1562 filled = RGRADIENT;
1563 else
1564 filled = GRADIENT;
1565 gvrender_box(job, job->clip, filled);
1566 free (clrs[0]);
1567 free(clrs[1]);
1568 }
1569 else {
1571 gvrender_set_pencolor(job, "transparent");
1572 gvrender_box(job, job->clip, FILL); /* filled */
1573 }
1574 }
1575
1576 if ((xd = GD_drawing(g)->xdots))
1577 emit_xdot (job, xd);
1578}
1579
1580static void setup_page(GVJ_t * job)
1581{
1582 point pagesArrayElem = job->pagesArrayElem, pagesArraySize = job->pagesArraySize;
1583
1584 if (job->rotation) {
1585 pagesArrayElem = exch_xy(pagesArrayElem);
1586 pagesArraySize = exch_xy(pagesArraySize);
1587 }
1588
1589 /* establish current box in graph units */
1590 job->pageBox.LL.x = pagesArrayElem.x * job->pageSize.x - job->pad.x;
1591 job->pageBox.LL.y = pagesArrayElem.y * job->pageSize.y - job->pad.y;
1592 job->pageBox.UR.x = job->pageBox.LL.x + job->pageSize.x;
1593 job->pageBox.UR.y = job->pageBox.LL.y + job->pageSize.y;
1594
1595 /* maximum boundingBox in device units and page orientation */
1596 if (job->common->viewNum == 0)
1597 job->boundingBox = job->pageBoundingBox;
1598 else
1600
1601 if (job->flags & GVDEVICE_EVENTS) {
1602 job->clip.LL.x = job->focus.x - job->view.x / 2.;
1603 job->clip.LL.y = job->focus.y - job->view.y / 2.;
1604 job->clip.UR.x = job->focus.x + job->view.x / 2.;
1605 job->clip.UR.y = job->focus.y + job->view.y / 2.;
1606 }
1607 else {
1608 job->clip.LL.x = job->focus.x + job->pageSize.x * (pagesArrayElem.x - pagesArraySize.x / 2.);
1609 job->clip.LL.y = job->focus.y + job->pageSize.y * (pagesArrayElem.y - pagesArraySize.y / 2.);
1610 job->clip.UR.x = job->clip.LL.x + job->pageSize.x;
1611 job->clip.UR.y = job->clip.LL.y + job->pageSize.y;
1612 }
1613
1614 /* CAUTION - job->translation was difficult to get right. */
1615 // Test with and without asymmetric margins, e.g: -Gmargin="1,0"
1616 if (job->rotation) {
1617 job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
1618 if ((job->flags & GVRENDER_Y_GOES_DOWN) || Y_invert)
1619 job->translation.x = - job->clip.UR.x - job->canvasBox.LL.x / job->zoom;
1620 else
1621 job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
1622 }
1623 else {
1624 /* pre unscale margins to keep them constant under scaling */
1625 job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
1626 if ((job->flags & GVRENDER_Y_GOES_DOWN) || Y_invert)
1627 job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
1628 else
1629 job->translation.y = - job->clip.LL.y + job->canvasBox.LL.y / job->zoom;
1630 }
1631}
1632
1633static bool node_in_layer(GVJ_t *job, graph_t * g, node_t * n)
1634{
1635 char *pn, *pe;
1636 edge_t *e;
1637
1638 if (job->numLayers <= 1)
1639 return true;
1640 pn = late_string(n, N_layer, "");
1641 if (selectedlayer(job, pn))
1642 return true;
1643 if (pn[0])
1644 return false; /* Only check edges if pn = "" */
1645 if ((e = agfstedge(g, n)) == NULL)
1646 return true;
1647 for (e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
1648 pe = late_string(e, E_layer, "");
1649 if (pe[0] == '\0' || selectedlayer(job, pe))
1650 return true;
1651 }
1652 return false;
1653}
1654
1655static bool edge_in_layer(GVJ_t *job, edge_t * e)
1656{
1657 char *pe, *pn;
1658 int cnt;
1659
1660 if (job->numLayers <= 1)
1661 return true;
1662 pe = late_string(e, E_layer, "");
1663 if (selectedlayer(job, pe))
1664 return true;
1665 if (pe[0])
1666 return false;
1667 for (cnt = 0; cnt < 2; cnt++) {
1668 pn = late_string(cnt < 1 ? agtail(e) : aghead(e), N_layer, "");
1669 if (pn[0] == '\0' || selectedlayer(job, pn))
1670 return true;
1671 }
1672 return false;
1673}
1674
1675static bool clust_in_layer(GVJ_t *job, graph_t * sg)
1676{
1677 char *pg;
1678 node_t *n;
1679
1680 if (job->numLayers <= 1)
1681 return true;
1682 pg = late_string(sg, agattr(sg, AGRAPH, "layer", 0), "");
1683 if (selectedlayer(job, pg))
1684 return true;
1685 if (pg[0])
1686 return false;
1687 for (n = agfstnode(sg); n; n = agnxtnode(sg, n))
1688 if (node_in_layer(job, sg, n))
1689 return true;
1690 return false;
1691}
1692
1693static bool node_in_box(node_t *n, boxf b)
1694{
1695 return boxf_overlap(ND_bb(n), b);
1696}
1697
1699
1700static void emit_begin_node(GVJ_t * job, node_t * n)
1701{
1702 obj_state_t *obj;
1703 int flags = job->flags;
1704 int shape;
1705 size_t nump = 0;
1706 polygon_t *poly = NULL;
1707 pointf *vertices, *p = NULL;
1708 pointf coord;
1709 char *s;
1710
1711 obj = push_obj_state(job);
1712 obj->type = NODE_OBJTYPE;
1713 obj->u.n = n;
1714 obj->emit_state = EMIT_NDRAW;
1715
1716 if (flags & GVRENDER_DOES_Z) {
1717 if (GD_odim(agraphof(n)) >=3)
1718 obj->z = POINTS(ND_pos(n)[2]);
1719 else
1720 obj->z = 0.0;
1721 }
1722 initObjMapData (job, ND_label(n), n);
1724 && (obj->url || obj->explicit_tooltip)) {
1725
1726 /* checking shape of node */
1727 shape = shapeOf(n);
1728 /* node coordinate */
1729 coord = ND_coord(n);
1730 /* checking if filled style has been set for node */
1731 bool filled = isFilled(n);
1732
1733 bool is_rect = false;
1734 if (shape == SH_POLY || shape == SH_POINT) {
1735 poly = ND_shape_info(n);
1736
1737 /* checking if polygon is regular rectangle */
1738 if (isRect(poly) && (poly->peripheries || filled))
1739 is_rect = true;
1740 }
1741
1742 /* When node has polygon shape and requested output supports polygons
1743 * we use a polygon to map the clickable region that is a:
1744 * circle, ellipse, polygon with n side, or point.
1745 * For regular rectangular shape we have use node's bounding box to map clickable region
1746 */
1747 if (poly && !is_rect && (flags & GVRENDER_DOES_MAP_POLYGON)) {
1748
1749 const size_t sides = poly->sides < 3 ? 1 : poly->sides;
1750 const size_t peripheries = poly->peripheries < 2 ? 1 : poly->peripheries;
1751
1752 vertices = poly->vertices;
1753
1754 int nump_int = 0;
1755 if ((s = agget(n, "samplepoints")))
1756 nump_int = atoi(s);
1757 /* We want at least 4 points. For server-side maps, at most 100
1758 * points are allowed. To simplify things to fit with the 120 points
1759 * used for skewed ellipses, we set the bound at 60.
1760 */
1761 nump = (nump_int < 4 || nump_int > 60) ? DFLT_SAMPLE : (size_t)nump_int;
1762 /* use bounding box of text label or node image for mapping
1763 * when polygon has no peripheries and node is not filled
1764 */
1765 if (poly->peripheries == 0 && !filled) {
1767 nump = 2;
1768 p = gv_calloc(nump, sizeof(pointf));
1769 P2RECT(coord, p, ND_lw(n), ND_ht(n) / 2.0 );
1770 }
1771 /* circle or ellipse */
1772 else if (poly->sides < 3 && is_exactly_zero(poly->skew) &&
1773 is_exactly_zero(poly->distortion)) {
1774 if (poly->regular) {
1776 nump = 2; /* center of circle and top right corner of bb */
1777 p = gv_calloc(nump, sizeof(pointf));
1778 p[0].x = coord.x;
1779 p[0].y = coord.y;
1780 /* even vertices contain LL corner of bb */
1781 /* odd vertices contain UR corner of bb */
1782 p[1].x = coord.x + vertices[2*peripheries - 1].x;
1783 p[1].y = coord.y + vertices[2*peripheries - 1].y;
1784 }
1785 else { /* ellipse is treated as polygon */
1787 p = pEllipse(vertices[2 * peripheries - 1].x,
1788 vertices[2 * peripheries - 1].y, nump);
1789 for (size_t i = 0; i < nump; i++) {
1790 p[i].x += coord.x;
1791 p[i].y += coord.y;
1792 }
1793 }
1794 }
1795 /* all other polygonal shape */
1796 else {
1797 assert(peripheries >= 1);
1798 size_t offset = (peripheries - 1) * poly->sides;
1800 /* distorted or skewed ellipses and circles are polygons with 120
1801 * sides. For mapping we convert them into polygon with sample sides
1802 */
1803 if (poly->sides >= nump) {
1804 size_t delta = poly->sides / nump;
1805 p = gv_calloc(nump, sizeof(pointf));
1806 for (size_t i = 0, j = 0; j < nump; i += delta, j++) {
1807 p[j].x = coord.x + vertices[i + offset].x;
1808 p[j].y = coord.y + vertices[i + offset].y;
1809 }
1810 } else {
1811 nump = sides;
1812 p = gv_calloc(nump, sizeof(pointf));
1813 for (size_t i = 0; i < nump; i++) {
1814 p[i].x = coord.x + vertices[i + offset].x;
1815 p[i].y = coord.y + vertices[i + offset].y;
1816 }
1817 }
1818 }
1819 }
1820 else {
1821 /* we have to use the node's bounding box to map clickable region
1822 * when requested output format is not capable of polygons.
1823 */
1825 nump = 2;
1826 p = gv_calloc(nump, sizeof(pointf));
1827 p[0].x = coord.x - ND_lw(n);
1828 p[0].y = coord.y - (ND_ht(n) / 2);
1829 p[1].x = coord.x + ND_rw(n);
1830 p[1].y = coord.y + (ND_ht(n) / 2);
1831 }
1833 gvrender_ptf_A(job, p, p, nump);
1834 obj->url_map_p = p;
1835 obj->url_map_n = nump;
1836 }
1837
1838 saved_color_scheme = setColorScheme(agget(n, "colorscheme"));
1840}
1841
1842static void emit_end_node(GVJ_t * job)
1843{
1844 gvrender_end_node(job);
1845
1846 char *color_scheme = setColorScheme(saved_color_scheme);
1847 free(color_scheme);
1850
1851 pop_obj_state(job);
1852}
1853
1854static void emit_node(GVJ_t * job, node_t * n)
1855{
1856 GVC_t *gvc = job->gvc;
1857 char *s;
1858 char *style;
1859 char **styles = NULL;
1860 char **sp;
1861 char *p;
1862
1863 if (ND_shape(n) /* node has a shape */
1864 && node_in_layer(job, agraphof(n), n) /* and is in layer */
1865 && node_in_box(n, job->clip) /* and is in page/view */
1866 && ND_state(n) != gvc->common.viewNum) /* and not already drawn */
1867 {
1868 ND_state(n) = gvc->common.viewNum; /* mark node as drawn */
1869
1870 gvrender_comment(job, agnameof(n));
1871 s = late_string(n, N_comment, "");
1872 if (s[0])
1873 gvrender_comment(job, s);
1874
1875 style = late_string(n, N_style, "");
1876 if (style[0]) {
1877 styles = parse_style(style);
1878 sp = styles;
1879 while ((p = *sp++)) {
1880 if (streq(p, "invis")) return;
1881 }
1882 }
1883
1884 emit_begin_node(job, n);
1885 ND_shape(n)->fns->codefn(job, n);
1886 if (ND_xlabel(n) && ND_xlabel(n)->set)
1888 emit_end_node(job);
1889 }
1890}
1891
1892/* calculate an offset vector, length d, perpendicular to line p,q */
1894{
1895 pointf res;
1896 double x = p.x - q.x, y = p.y - q.y;
1897
1898 /* keep d finite as line length approaches 0 */
1899 d /= sqrt(x * x + y * y + EPSILON);
1900 res.x = y * d;
1901 res.y = -x * d;
1902 return res;
1903}
1904
1905/* calculate offset vector, length d, perpendicular to spline p,q,r,s at q&r */
1907 double d)
1908{
1909 pointf res;
1910 double len;
1911 double x = q.x - r.x, y = q.y - r.y;
1912
1913 len = hypot(x, y);
1914 if (len < EPSILON) {
1915 /* control points are on top of each other
1916 use slope between endpoints instead */
1917 x = p.x - s.x, y = p.y - s.y;
1918 /* keep d finite as line length approaches 0 */
1919 len = sqrt(x * x + y * y + EPSILON);
1920 }
1921 d /= len;
1922 res.x = y * d;
1923 res.y = -x * d;
1924 return res;
1925}
1926
1927static void emit_attachment(GVJ_t * job, textlabel_t * lp, splines * spl)
1928{
1929 pointf sz, AF[3];
1930 const char *s;
1931
1932 for (s = lp->text; *s; s++) {
1933 if (!gv_isspace(*s))
1934 break;
1935 }
1936 if (*s == '\0')
1937 return;
1938
1939 sz = lp->dimen;
1940 AF[0] = (pointf){lp->pos.x + sz.x / 2., lp->pos.y - sz.y / 2.};
1941 AF[1] = (pointf){AF[0].x - sz.x, AF[0].y};
1942 AF[2] = dotneato_closest(spl, lp->pos);
1943 /* Don't use edge style to draw attachment */
1945 /* Use font color to draw attachment
1946 - need something unambiguous in case of multicolored parallel edges
1947 - defaults to black for html-like labels
1948 */
1950 gvrender_polyline(job, AF, 3);
1951}
1952
1953/* edges’ colors can be multiple colors separated by ":"
1954 * so we compute a default pencolor with the same number of colors. */
1955static char *default_pencolor(agxbuf *buf, const char *pencolor,
1956 const char *deflt) {
1957 agxbput(buf, deflt);
1958 for (const char *p = pencolor; *p; p++) {
1959 if (*p == ':')
1960 agxbprint(buf, ":%s", deflt);
1961 }
1962 return agxbuse(buf);
1963}
1964
1965static double approxLen (pointf* pts)
1966{
1967 double d = DIST(pts[0],pts[1]);
1968 d += DIST(pts[1],pts[2]);
1969 d += DIST(pts[2],pts[3]);
1970 return d;
1971}
1972
1973/* Given B-spline bz and 0 < t < 1, split bz so that left corresponds to
1974 * the fraction t of the arc length. The new parts are store in left and right.
1975 * The caller needs to free the allocated points.
1976 *
1977 * In the current implementation, we find the Bezier that should contain t by
1978 * treating the control points as a polyline.
1979 * We then split that Bezier.
1980 */
1981static void splitBSpline(bezier *bz, double t, bezier *left, bezier *right) {
1982 const size_t cnt = (bz->size - 1) / 3;
1983 double last, len, sum;
1984 pointf* pts;
1985
1986 if (cnt == 1) {
1987 left->size = 4;
1988 left->list = gv_calloc(4, sizeof(pointf));
1989 right->size = 4;
1990 right->list = gv_calloc(4, sizeof(pointf));
1991 Bezier (bz->list, t, left->list, right->list);
1992 return;
1993 }
1994
1995 double* lens = gv_calloc(cnt, sizeof(double));
1996 sum = 0;
1997 pts = bz->list;
1998 for (size_t i = 0; i < cnt; i++) {
1999 lens[i] = approxLen (pts);
2000 sum += lens[i];
2001 pts += 3;
2002 }
2003 len = t*sum;
2004 sum = 0;
2005 size_t i;
2006 for (i = 0; i < cnt; i++) {
2007 sum += lens[i];
2008 if (sum >= len)
2009 break;
2010 }
2011
2012 left->size = 3*(i+1) + 1;
2013 left->list = gv_calloc(left->size, sizeof(pointf));
2014 right->size = 3*(cnt-i) + 1;
2015 right->list = gv_calloc(right->size, sizeof(pointf));
2016 size_t j;
2017 for (j = 0; j < left->size; j++)
2018 left->list[j] = bz->list[j];
2019 size_t k = j - 4;
2020 for (j = 0; j < right->size; j++)
2021 right->list[j] = bz->list[k++];
2022
2023 last = lens[i];
2024 const double r = (len - (sum - last)) / last;
2025 Bezier (bz->list + 3*i, r, left->list + 3*i, right->list);
2026
2027 free (lens);
2028}
2029
2030/* Draw an edge as a sequence of colors.
2031 * Not sure how to handle multiple B-splines, so do a naive
2032 * implementation.
2033 * Return non-zero if color spec is incorrect
2034 */
2035static int multicolor(GVJ_t *job, edge_t *e, char **styles, const char *colors,
2036 double arrowsize, double penwidth) {
2037 bezier bz;
2038 bezier bz0, bz_l, bz_r;
2039 int rv;
2040 colorsegs_t segs;
2041 char* endcolor = NULL;
2042 double left;
2043 int first; /* first segment with t > 0 */
2044
2045 rv = parseSegs(colors, &segs);
2046 if (rv > 1) {
2047 Agraph_t* g = agraphof(agtail(e));
2048 agerr (AGPREV, "in edge %s%s%s\n", agnameof(agtail(e)), (agisdirected(g)?" -> ":" -- "), agnameof(aghead(e)));
2049
2050 if (rv == 2)
2051 return 1;
2052 }
2053 else if (rv == 1)
2054 return 1;
2055
2056
2057 for (size_t i = 0; i < ED_spl(e)->size; i++) {
2058 left = 1;
2059 bz = ED_spl(e)->list[i];
2060 first = 1;
2061 for (size_t j = 0; j < colorsegs_size(&segs); ++j) {
2062 const colorseg_t s = colorsegs_get(&segs, j);
2063 if (s.color == NULL) break;
2064 if (AEQ0(s.t)) continue;
2065 gvrender_set_pencolor(job, s.color);
2066 left -= s.t;
2067 endcolor = s.color;
2068 if (first) {
2069 first = 0;
2070 splitBSpline(&bz, s.t, &bz_l, &bz_r);
2071 gvrender_beziercurve(job, bz_l.list, bz_l.size, 0);
2072 free (bz_l.list);
2073 if (AEQ0(left)) {
2074 free (bz_r.list);
2075 break;
2076 }
2077 }
2078 else if (AEQ0(left)) {
2079 gvrender_beziercurve(job, bz_r.list, bz_r.size, 0);
2080 free (bz_r.list);
2081 break;
2082 }
2083 else {
2084 bz0 = bz_r;
2085 splitBSpline(&bz0, s.t / (left + s.t), &bz_l, &bz_r);
2086 free (bz0.list);
2087 gvrender_beziercurve(job, bz_l.list, bz_l.size, 0);
2088 free (bz_l.list);
2089 }
2090
2091 }
2092 /* arrow_gen resets the job style (How? FIXME)
2093 * If we have more splines to do, restore the old one.
2094 * Use local copy of penwidth to work around reset.
2095 */
2096 if (bz.sflag) {
2097 gvrender_set_pencolor(job, colorsegs_front(&segs)->color);
2098 gvrender_set_fillcolor(job, colorsegs_front(&segs)->color);
2099 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2100 }
2101 if (bz.eflag) {
2102 gvrender_set_pencolor(job, endcolor);
2103 gvrender_set_fillcolor(job, endcolor);
2104 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2105 }
2106 if (ED_spl(e)->size > 1 && (bz.sflag || bz.eflag) && styles)
2107 gvrender_set_style(job, styles);
2108 }
2109 colorsegs_free(&segs);
2110 return 0;
2111}
2112
2113static void free_stroke(stroke_t sp) {
2114 free(sp.vertices);
2115}
2116
2117typedef double (*radfunc_t)(double,double,double);
2118
2119static double forfunc (double curlen, double totallen, double initwid)
2120{
2121 return (1 - curlen / totallen) * initwid / 2.0;
2122}
2123
2124static double revfunc (double curlen, double totallen, double initwid)
2125{
2126 return curlen / totallen * initwid / 2.0;
2127}
2128
2129static double nonefunc (double curlen, double totallen, double initwid)
2130{
2131 (void)curlen;
2132 (void)totallen;
2133
2134 return initwid / 2.0;
2135}
2136
2137static double bothfunc (double curlen, double totallen, double initwid)
2138{
2139 double fr = curlen/totallen;
2140 if (fr <= 0.5) return fr * initwid;
2141 return (1 - fr) * initwid;
2142}
2143
2144static radfunc_t
2146{
2147 char* attr;
2148 if (E_dir && ((attr = agxget(e, E_dir)))[0]) {
2149 if (streq(attr, "forward")) return forfunc;
2150 if (streq(attr, "back")) return revfunc;
2151 if (streq(attr, "both")) return bothfunc;
2152 if (streq(attr, "none")) return nonefunc;
2153 }
2155}
2156
2157static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
2158{
2159 int cnum, numsemi = 0;
2160 char *color, *pencolor, *fillcolor;
2161 char *headcolor, *tailcolor, *lastcolor;
2162 char *colors = NULL;
2163 bezier bz;
2164 splines offspl, tmpspl;
2165 pointf pf0, pf1, pf2 = { 0, 0 }, pf3, *offlist, *tmplist;
2166 double arrowsize, numc2, penwidth=job->obj->penwidth;
2167 char* p;
2168 bool tapered = false;
2169 agxbuf buf = {0};
2170
2171#define SEP 2.0
2172
2173 char *previous_color_scheme = setColorScheme(agget(e, "colorscheme"));
2174 if (ED_spl(e)) {
2175 arrowsize = late_double(e, E_arrowsz, 1.0, 0.0);
2176 color = late_string(e, E_color, "");
2177
2178 if (styles) {
2179 char** sp = styles;
2180 while ((p = *sp++)) {
2181 if (streq(p, "tapered")) {
2182 tapered = true;
2183 break;
2184 }
2185 }
2186 }
2187
2188 /* need to know how many colors separated by ':' */
2189 size_t numc = 0;
2190 for (p = color; *p; p++) {
2191 if (*p == ':')
2192 numc++;
2193 else if (*p == ';')
2194 numsemi++;
2195 }
2196
2197 if (numsemi && numc) {
2198 if (multicolor(job, e, styles, color, arrowsize, penwidth)) {
2200 }
2201 else
2202 goto done;
2203 }
2204
2205 fillcolor = pencolor = color;
2206 if (ED_gui_state(e) & GUI_STATE_ACTIVE) {
2207 pencolor = default_pencolor(&buf, pencolor, DEFAULT_ACTIVEPENCOLOR);
2208 fillcolor = DEFAULT_ACTIVEFILLCOLOR;
2209 }
2210 else if (ED_gui_state(e) & GUI_STATE_SELECTED) {
2211 pencolor = default_pencolor(&buf, pencolor, DEFAULT_SELECTEDPENCOLOR);
2212 fillcolor = DEFAULT_SELECTEDFILLCOLOR;
2213 }
2214 else if (ED_gui_state(e) & GUI_STATE_DELETED) {
2215 pencolor = default_pencolor(&buf, pencolor, DEFAULT_DELETEDPENCOLOR);
2216 fillcolor = DEFAULT_DELETEDFILLCOLOR;
2217 }
2218 else if (ED_gui_state(e) & GUI_STATE_VISITED) {
2219 pencolor = default_pencolor(&buf, pencolor, DEFAULT_VISITEDPENCOLOR);
2220 fillcolor = DEFAULT_VISITEDFILLCOLOR;
2221 }
2222 else
2223 fillcolor = late_nnstring(e, E_fillcolor, color);
2224 if (pencolor != color)
2225 gvrender_set_pencolor(job, pencolor);
2226 if (fillcolor != color)
2227 gvrender_set_fillcolor(job, fillcolor);
2228 color = pencolor;
2229
2230 if (tapered) {
2231 if (*color == '\0') color = DEFAULT_COLOR;
2232 if (*fillcolor == '\0') fillcolor = DEFAULT_COLOR;
2233 gvrender_set_pencolor(job, "transparent");
2235 bz = ED_spl(e)->list[0];
2236 stroke_t stp = taper(&bz, taperfun (e), penwidth);
2237 assert(stp.nvertices <= INT_MAX);
2238 gvrender_polygon(job, stp.vertices, stp.nvertices, 1);
2239 free_stroke(stp);
2241 if (fillcolor != color)
2242 gvrender_set_fillcolor(job, fillcolor);
2243 if (bz.sflag) {
2244 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2245 }
2246 if (bz.eflag) {
2247 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2248 }
2249 }
2250 /* if more than one color - then generate parallel beziers, one per color */
2251 else if (numc) {
2252 /* calculate and save offset vector spline and initialize first offset spline */
2253 tmpspl.size = offspl.size = ED_spl(e)->size;
2254 offspl.list = gv_calloc(offspl.size, sizeof(bezier));
2255 tmpspl.list = gv_calloc(tmpspl.size, sizeof(bezier));
2256 numc2 = (2 + (double)numc) / 2.0;
2257 for (size_t i = 0; i < offspl.size; i++) {
2258 bz = ED_spl(e)->list[i];
2259 tmpspl.list[i].size = offspl.list[i].size = bz.size;
2260 offlist = offspl.list[i].list = gv_calloc(bz.size, sizeof(pointf));
2261 tmplist = tmpspl.list[i].list = gv_calloc(bz.size, sizeof(pointf));
2262 pf3 = bz.list[0];
2263 size_t j;
2264 for (j = 0; j < bz.size - 1; j += 3) {
2265 pf0 = pf3;
2266 pf1 = bz.list[j + 1];
2267 /* calculate perpendicular vectors for each bezier point */
2268 if (j == 0) /* first segment, no previous pf2 */
2269 offlist[j] = computeoffset_p(pf0, pf1, SEP);
2270 else /* i.e. pf2 is available from previous segment */
2271 offlist[j] = computeoffset_p(pf2, pf1, SEP);
2272 pf2 = bz.list[j + 2];
2273 pf3 = bz.list[j + 3];
2274 offlist[j + 1] = offlist[j + 2] =
2275 computeoffset_qr(pf0, pf1, pf2, pf3, SEP);
2276 /* initialize tmpspl to outermost position */
2277 tmplist[j].x = pf0.x - numc2 * offlist[j].x;
2278 tmplist[j].y = pf0.y - numc2 * offlist[j].y;
2279 tmplist[j + 1].x = pf1.x - numc2 * offlist[j + 1].x;
2280 tmplist[j + 1].y = pf1.y - numc2 * offlist[j + 1].y;
2281 tmplist[j + 2].x = pf2.x - numc2 * offlist[j + 2].x;
2282 tmplist[j + 2].y = pf2.y - numc2 * offlist[j + 2].y;
2283 }
2284 /* last segment, no next pf1 */
2285 offlist[j] = computeoffset_p(pf2, pf3, SEP);
2286 tmplist[j].x = pf3.x - numc2 * offlist[j].x;
2287 tmplist[j].y = pf3.y - numc2 * offlist[j].y;
2288 }
2289 lastcolor = headcolor = tailcolor = color;
2290 colors = gv_strdup(color);
2291 for (cnum = 0, color = strtok(colors, ":"); color;
2292 cnum++, color = strtok(0, ":")) {
2293 if (!color[0])
2295 if (color != lastcolor) {
2299 }
2300 lastcolor = color;
2301 }
2302 if (cnum == 0)
2303 headcolor = tailcolor = color;
2304 if (cnum == 1)
2305 tailcolor = color;
2306 for (size_t i = 0; i < tmpspl.size; i++) {
2307 tmplist = tmpspl.list[i].list;
2308 offlist = offspl.list[i].list;
2309 for (size_t j = 0; j < tmpspl.list[i].size; j++) {
2310 tmplist[j].x += offlist[j].x;
2311 tmplist[j].y += offlist[j].y;
2312 }
2313 gvrender_beziercurve(job, tmplist, tmpspl.list[i].size, 0);
2314 }
2315 }
2316 if (bz.sflag) {
2317 if (color != tailcolor) {
2318 color = tailcolor;
2322 }
2323 }
2324 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2325 arrowsize, penwidth, bz.sflag);
2326 }
2327 if (bz.eflag) {
2328 if (color != headcolor) {
2329 color = headcolor;
2333 }
2334 }
2335 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2336 arrowsize, penwidth, bz.eflag);
2337 }
2338 free(colors);
2339 for (size_t i = 0; i < offspl.size; i++) {
2340 free(offspl.list[i].list);
2341 free(tmpspl.list[i].list);
2342 }
2343 free(offspl.list);
2344 free(tmpspl.list);
2345 } else {
2347 if (color[0]) {
2349 gvrender_set_fillcolor(job, fillcolor);
2350 } else {
2352 if (fillcolor[0])
2353 gvrender_set_fillcolor(job, fillcolor);
2354 else
2356 }
2357 }
2358 for (size_t i = 0; i < ED_spl(e)->size; i++) {
2359 bz = ED_spl(e)->list[i];
2360 gvrender_beziercurve(job, bz.list, bz.size, 0);
2361 if (bz.sflag) {
2362 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2363 arrowsize, penwidth, bz.sflag);
2364 }
2365 if (bz.eflag) {
2366 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2367 arrowsize, penwidth, bz.eflag);
2368 }
2369 if (ED_spl(e)->size > 1 && (bz.sflag || bz.eflag) && styles)
2370 gvrender_set_style(job, styles);
2371 }
2372 }
2373 }
2374
2375done:;
2376 char *color_scheme = setColorScheme(previous_color_scheme);
2377 free(color_scheme);
2378 free(previous_color_scheme);
2379 agxbfree(&buf);
2380}
2381
2382static bool edge_in_box(edge_t *e, boxf b)
2383{
2384 splines *spl;
2385 textlabel_t *lp;
2386
2387 spl = ED_spl(e);
2388 if (spl && boxf_overlap(spl->bb, b))
2389 return true;
2390
2391 lp = ED_label(e);
2392 if (lp && overlap_label(lp, b))
2393 return true;
2394
2395 lp = ED_xlabel(e);
2396 if (lp && lp->set && overlap_label(lp, b))
2397 return true;
2398
2399 return false;
2400}
2401
2402static void emit_begin_edge(GVJ_t *job, edge_t *e, char **styles) {
2403 obj_state_t *obj;
2404 int flags = job->flags;
2405 char *s;
2406 textlabel_t *lab = NULL, *tlab = NULL, *hlab = NULL;
2407 char *dflt_url = NULL;
2408 char *dflt_target = NULL;
2409 double penwidth;
2410
2411 obj = push_obj_state(job);
2412 obj->type = EDGE_OBJTYPE;
2413 obj->u.e = e;
2414 obj->emit_state = EMIT_EDRAW;
2415 if (ED_label(e) && !ED_label(e)->html && mapbool(agget(e, "labelaligned")))
2416 obj->labeledgealigned = true;
2417
2418 /* We handle the edge style and penwidth here because the width
2419 * is needed below for calculating polygonal image maps
2420 */
2421 if (styles && ED_spl(e))
2422 gvrender_set_style(job, styles);
2423
2424 if (E_penwidth && (s = agxget(e, E_penwidth)) && s[0]) {
2425 penwidth = late_double(e, E_penwidth, 1.0, 0.0);
2427 }
2428
2429 if (flags & GVRENDER_DOES_Z) {
2430 if (GD_odim(agraphof(agtail(e))) >= 3) {
2431 obj->tail_z = POINTS(ND_pos(agtail(e))[2]);
2432 obj->head_z = POINTS(ND_pos(aghead(e))[2]);
2433 } else {
2434 obj->tail_z = obj->head_z = 0.0;
2435 }
2436 }
2437
2439 if ((lab = ED_label(e)))
2440 obj->label = lab->text;
2441 obj->taillabel = obj->headlabel = obj->xlabel = obj->label;
2442 if ((tlab = ED_xlabel(e)))
2443 obj->xlabel = tlab->text;
2444 if ((tlab = ED_tail_label(e)))
2445 obj->taillabel = tlab->text;
2446 if ((hlab = ED_head_label(e)))
2447 obj->headlabel = hlab->text;
2448 }
2449
2450 if (flags & GVRENDER_DOES_MAPS) {
2451 agxbuf xb = {0};
2452
2453 s = getObjId(job, e, &xb);
2454 obj->id = strdup_and_subst_obj(s, e);
2455 agxbfree(&xb);
2456
2457 if (((s = agget(e, "href")) && s[0]) || ((s = agget(e, "URL")) && s[0]))
2458 dflt_url = strdup_and_subst_obj(s, e);
2459 if (((s = agget(e, "edgehref")) && s[0]) ||
2460 ((s = agget(e, "edgeURL")) && s[0]))
2461 obj->url = strdup_and_subst_obj(s, e);
2462 else if (dflt_url)
2463 obj->url = gv_strdup(dflt_url);
2464 if (((s = agget(e, "labelhref")) && s[0]) ||
2465 ((s = agget(e, "labelURL")) && s[0]))
2466 obj->labelurl = strdup_and_subst_obj(s, e);
2467 else if (dflt_url)
2468 obj->labelurl = gv_strdup(dflt_url);
2469 if (((s = agget(e, "tailhref")) && s[0]) ||
2470 ((s = agget(e, "tailURL")) && s[0])) {
2471 obj->tailurl = strdup_and_subst_obj(s, e);
2472 obj->explicit_tailurl = true;
2473 } else if (dflt_url)
2474 obj->tailurl = gv_strdup(dflt_url);
2475 if (((s = agget(e, "headhref")) && s[0]) ||
2476 ((s = agget(e, "headURL")) && s[0])) {
2477 obj->headurl = strdup_and_subst_obj(s, e);
2478 obj->explicit_headurl = true;
2479 } else if (dflt_url)
2480 obj->headurl = gv_strdup(dflt_url);
2481 }
2482
2484 if ((s = agget(e, "target")) && s[0])
2485 dflt_target = strdup_and_subst_obj(s, e);
2486 if ((s = agget(e, "edgetarget")) && s[0]) {
2487 obj->explicit_edgetarget = true;
2488 obj->target = strdup_and_subst_obj(s, e);
2489 } else if (dflt_target)
2490 obj->target = gv_strdup(dflt_target);
2491 if ((s = agget(e, "labeltarget")) && s[0])
2493 else if (dflt_target)
2494 obj->labeltarget = gv_strdup(dflt_target);
2495 if ((s = agget(e, "tailtarget")) && s[0]) {
2497 obj->explicit_tailtarget = true;
2498 } else if (dflt_target)
2499 obj->tailtarget = gv_strdup(dflt_target);
2500 if ((s = agget(e, "headtarget")) && s[0]) {
2501 obj->explicit_headtarget = true;
2503 } else if (dflt_target)
2504 obj->headtarget = gv_strdup(dflt_target);
2505 }
2506
2508 if (((s = agget(e, "tooltip")) && s[0]) ||
2509 ((s = agget(e, "edgetooltip")) && s[0])) {
2510 char *tooltip = preprocessTooltip(s, e);
2511 obj->tooltip = strdup_and_subst_obj(tooltip, e);
2512 free(tooltip);
2513 obj->explicit_tooltip = true;
2514 } else if (obj->label)
2515 obj->tooltip = gv_strdup(obj->label);
2516
2517 if ((s = agget(e, "labeltooltip")) && s[0]) {
2518 char *tooltip = preprocessTooltip(s, e);
2519 obj->labeltooltip = strdup_and_subst_obj(tooltip, e);
2520 free(tooltip);
2521 obj->explicit_labeltooltip = true;
2522 } else if (obj->label)
2523 obj->labeltooltip = gv_strdup(obj->label);
2524
2525 if ((s = agget(e, "tailtooltip")) && s[0]) {
2526 char *tooltip = preprocessTooltip(s, e);
2527 obj->tailtooltip = strdup_and_subst_obj(tooltip, e);
2528 free(tooltip);
2529 obj->explicit_tailtooltip = true;
2530 } else if (obj->taillabel)
2531 obj->tailtooltip = gv_strdup(obj->taillabel);
2532
2533 if ((s = agget(e, "headtooltip")) && s[0]) {
2534 char *tooltip = preprocessTooltip(s, e);
2535 obj->headtooltip = strdup_and_subst_obj(tooltip, e);
2536 free(tooltip);
2537 obj->explicit_headtooltip = true;
2538 } else if (obj->headlabel)
2539 obj->headtooltip = gv_strdup(obj->headlabel);
2540 }
2541
2542 free(dflt_url);
2543 free(dflt_target);
2544
2546 if (ED_spl(e) && (obj->url || obj->tooltip) &&
2548 splines *spl;
2549 double w2 = fmax(job->obj->penwidth / 2.0, 2.0);
2550
2551 spl = ED_spl(e);
2552 const size_t ns = spl->size; /* number of splines */
2553 points_t pbs = {0};
2554 pbs_size_t pbs_n = {0};
2555 for (size_t i = 0; i < ns; i++)
2556 map_output_bspline(&pbs, &pbs_n, spl->list + i, w2);
2557 if (!(flags & GVRENDER_DOES_TRANSFORM)) {
2558 size_t nump = 0;
2559 for (size_t i = 0; i < pbs_size_size(&pbs_n); ++i) {
2560 nump += pbs_size_get(&pbs_n, i);
2561 }
2562 gvrender_ptf_A(job, points_front(&pbs), points_front(&pbs), nump);
2563 }
2564 obj->url_bsplinemap_p = points_front(&pbs);
2566 obj->url_map_p = points_detach(&pbs);
2567 obj->url_map_n = *pbs_size_front(&pbs_n);
2568 obj->url_bsplinemap_poly_n = pbs_size_size(&pbs_n);
2569 obj->url_bsplinemap_n = pbs_size_detach(&pbs_n);
2570 }
2571 }
2572
2574 if (obj->url || obj->explicit_tooltip)
2575 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
2576}
2577
2578static void
2579emit_edge_label(GVJ_t* job, textlabel_t* lbl, emit_state_t lkind, int explicit,
2580 char* url, char* tooltip, char* target, char *id, splines* spl)
2581{
2582 int flags = job->flags;
2583 emit_state_t old_emit_state;
2584 char* newid;
2585 agxbuf xb = {0};
2586 char* type;
2587
2588 if (lbl == NULL || !lbl->set) return;
2589 if (id) { /* non-NULL if needed */
2590 switch (lkind) {
2591 case EMIT_ELABEL :
2592 type = "label";
2593 break;
2594 case EMIT_HLABEL :
2595 type = "headlabel";
2596 break;
2597 case EMIT_TLABEL :
2598 type = "taillabel";
2599 break;
2600 default :
2601 UNREACHABLE();
2602 }
2603 agxbprint(&xb, "%s-%s", id, type);
2604 newid = agxbuse(&xb);
2605 }
2606 else
2607 newid = NULL;
2608 old_emit_state = job->obj->emit_state;
2609 job->obj->emit_state = lkind;
2610 if ((url || explicit) && !(flags & EMIT_CLUSTERS_LAST)) {
2611 map_label(job, lbl);
2612 gvrender_begin_anchor(job, url, tooltip, target, newid);
2613 }
2614 emit_label(job, lkind, lbl);
2615 if (spl) emit_attachment(job, lbl, spl);
2616 if (url || explicit) {
2617 if (flags & EMIT_CLUSTERS_LAST) {
2618 map_label(job, lbl);
2619 gvrender_begin_anchor(job, url, tooltip, target, newid);
2620 }
2622 }
2623 agxbfree(&xb);
2624 job->obj->emit_state = old_emit_state;
2625}
2626
2627/* Common logic for setting hot spots at the beginning and end of
2628 * an edge.
2629 * If we are given a value (url, tooltip, target) explicitly set for
2630 * the head/tail, we use that.
2631 * Otherwise, if we are given a value explicitly set for the edge,
2632 * we use that.
2633 * Otherwise, we use whatever the argument value is.
2634 * We also note whether or not the tooltip was explicitly set.
2635 * If the url is non-NULL or the tooltip was explicit, we set
2636 * a hot spot around point p.
2637 */
2638static void nodeIntersect(GVJ_t *job, pointf p, bool explicit_iurl, char *iurl,
2639 bool explicit_itooltip) {
2640 obj_state_t *obj = job->obj;
2641 char* url;
2642 bool explicit;
2643
2644 if (explicit_iurl) url = iurl;
2645 else url = obj->url;
2646 if (explicit_itooltip) {
2647 explicit = true;
2648 }
2649 else if (obj->explicit_tooltip) {
2650 explicit = true;
2651 }
2652 else {
2653 explicit = false;
2654 }
2655
2656 if (url || explicit) {
2657 map_point(job, p);
2658 }
2659}
2660
2661static void emit_end_edge(GVJ_t * job)
2662{
2663 obj_state_t *obj = job->obj;
2664 edge_t *e = obj->u.e;
2665
2666 if (obj->url || obj->explicit_tooltip) {
2668 if (obj->url_bsplinemap_poly_n) {
2669 for (size_t nump = obj->url_bsplinemap_n[0], i = 1;
2670 i < obj->url_bsplinemap_poly_n; i++) {
2671 /* additional polygon maps around remaining bezier pieces */
2672 obj->url_map_n = obj->url_bsplinemap_n[i];
2673 obj->url_map_p = &(obj->url_bsplinemap_p[nump]);
2675 obj->url, obj->tooltip, obj->target, obj->id);
2677 nump += obj->url_bsplinemap_n[i];
2678 }
2679 }
2680 }
2681 obj->url_map_n = 0; /* null out copy so that it doesn't get freed twice */
2682 obj->url_map_p = NULL;
2683
2684 if (ED_spl(e)) {
2685 pointf p;
2686 bezier bz;
2687
2688 /* process intersection with tail node */
2689 bz = ED_spl(e)->list[0];
2690 if (bz.sflag) /* Arrow at start of splines */
2691 p = bz.sp;
2692 else /* No arrow at start of splines */
2693 p = bz.list[0];
2694 nodeIntersect(job, p, obj->explicit_tailurl != 0, obj->tailurl,
2695 obj->explicit_tailtooltip != 0);
2696
2697 /* process intersection with head node */
2698 bz = ED_spl(e)->list[ED_spl(e)->size - 1];
2699 if (bz.eflag) /* Arrow at end of splines */
2700 p = bz.ep;
2701 else /* No arrow at end of splines */
2702 p = bz.list[bz.size - 1];
2703 nodeIntersect(job, p, obj->explicit_headurl != 0, obj->headurl,
2704 obj->explicit_headtooltip != 0);
2705 }
2706
2709 obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
2710 ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
2713 obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
2714 ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
2717 obj->headurl, obj->headtooltip, obj->headtarget, obj->id,
2718 0);
2721 obj->tailurl, obj->tailtooltip, obj->tailtarget, obj->id,
2722 0);
2723
2724 gvrender_end_edge(job);
2725 pop_obj_state(job);
2726}
2727
2728static void emit_edge(GVJ_t * job, edge_t * e)
2729{
2730 char *s;
2731 char *style;
2732 char **styles = NULL;
2733 char **sp;
2734 char *p;
2735
2736 if (edge_in_box(e, job->clip) && edge_in_layer(job, e) ) {
2737
2738 agxbuf edge = {0};
2739 agxbput(&edge, agnameof(agtail(e)));
2740 if (agisdirected(agraphof(aghead(e))))
2741 agxbput(&edge, "->");
2742 else
2743 agxbput(&edge, "--");
2744 agxbput(&edge, agnameof(aghead(e)));
2746 agxbfree(&edge);
2747
2748 s = late_string(e, E_comment, "");
2749 if (s[0])
2750 gvrender_comment(job, s);
2751
2752 style = late_string(e, E_style, "");
2753 /* We shortcircuit drawing an invisible edge because the arrowhead
2754 * code resets the style to solid, and most of the code generators
2755 * (except PostScript) won't honor a previous style of invis.
2756 */
2757 if (style[0]) {
2758 styles = parse_style(style);
2759 sp = styles;
2760 while ((p = *sp++)) {
2761 if (streq(p, "invis")) return;
2762 }
2763 }
2764
2765 emit_begin_edge(job, e, styles);
2766 emit_edge_graphics (job, e, styles);
2767 emit_end_edge(job);
2768 }
2769}
2770
2771static const char adjust[] = {'l', 'n', 'r'};
2772
2773static void
2775{
2776 bb->UR.x = fmax(bb->UR.x, p.x);
2777 bb->LL.x = fmin(bb->LL.x, p.x);
2778 bb->UR.y = fmax(bb->UR.y, p.y);
2779 bb->LL.y = fmin(bb->LL.y, p.y);
2780}
2781
2782static boxf ptsBB(xdot_point *inpts, size_t numpts, boxf *bb) {
2783 boxf opbb;
2784
2785 opbb.LL.x = opbb.UR.x = inpts->x;
2786 opbb.LL.y = opbb.UR.y = inpts->y;
2787 for (size_t i = 1; i < numpts; i++) {
2788 inpts++;
2789 if (inpts->x < opbb.LL.x)
2790 opbb.LL.x = inpts->x;
2791 else if (inpts->x > opbb.UR.x)
2792 opbb.UR.x = inpts->x;
2793 if (inpts->y < opbb.LL.y)
2794 opbb.LL.y = inpts->y;
2795 else if (inpts->y > opbb.UR.y)
2796 opbb.UR.y = inpts->y;
2797
2798 }
2799 expandBB (bb, opbb.LL);
2800 expandBB (bb, opbb.UR);
2801 return opbb;
2802}
2803
2804static boxf
2805textBB (double x, double y, textspan_t* span)
2806{
2807 boxf bb;
2808 pointf sz = span->size;
2809
2810 switch (span->just) {
2811 case 'l':
2812 bb.LL.x = x;
2813 bb.UR.x = bb.LL.x + sz.x;
2814 break;
2815 case 'n':
2816 bb.LL.x = x - sz.x / 2.0;
2817 bb.UR.x = x + sz.x / 2.0;
2818 break;
2819 case 'r':
2820 bb.UR.x = x;
2821 bb.LL.x = bb.UR.x - sz.x;
2822 break;
2823 }
2824 bb.UR.y = y + span->yoffset_layout;
2825 bb.LL.y = bb.UR.y - sz.y;
2826 return bb;
2827}
2828
2829static void
2831{
2832 if (op->op.kind == xd_text)
2833 free_textspan (op->span, 1);
2834}
2835
2837{
2838 GVC_t *gvc = GD_gvc(g);
2839 exdot_op* op;
2840 double fontsize = 0.0;
2841 char* fontname = NULL;
2842 pointf pts[2];
2843 boxf bb0;
2844 boxf bb = GD_bb(g);
2845 xdot* xd = GD_drawing(g)->xdots;
2846 textfont_t tf, null_tf = {0};
2847 int fontflags = 0;
2848
2849 if (!xd) return bb;
2850
2851 if (bb.LL.x == bb.UR.x && bb.LL.y == bb.UR.y) {
2852 bb.LL.x = bb.LL.y = DBL_MAX;
2853 bb.UR.x = bb.UR.y = -DBL_MAX;
2854 }
2855
2856 op = (exdot_op*)xd->ops;
2857 for (size_t i = 0; i < xd->cnt; i++) {
2858 tf = null_tf;
2859 switch (op->op.kind) {
2860 case xd_filled_ellipse :
2861 case xd_unfilled_ellipse :
2862 pts[0].x = op->op.u.ellipse.x - op->op.u.ellipse.w;
2863 pts[0].y = op->op.u.ellipse.y - op->op.u.ellipse.h;
2864 pts[1].x = op->op.u.ellipse.x + op->op.u.ellipse.w;
2865 pts[1].y = op->op.u.ellipse.y + op->op.u.ellipse.h;
2866 op->bb.LL = pts[0];
2867 op->bb.UR = pts[1];
2868 expandBB (&bb, pts[0]);
2869 expandBB (&bb, pts[1]);
2870 break;
2871 case xd_filled_polygon :
2872 case xd_unfilled_polygon :
2873 op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
2874 break;
2875 case xd_filled_bezier :
2876 case xd_unfilled_bezier :
2877 op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
2878 break;
2879 case xd_polyline :
2880 op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
2881 break;
2882 case xd_text :
2883 op->span = gv_alloc(sizeof(textspan_t));
2884 op->span->str = gv_strdup (op->op.u.text.text);
2885 op->span->just = adjust [op->op.u.text.align];
2886 tf.name = fontname;
2887 tf.size = fontsize;
2888 tf.flags = fontflags;
2889 op->span->font = dtinsert(gvc->textfont_dt, &tf);
2890 textspan_size (gvc, op->span);
2891 bb0 = textBB (op->op.u.text.x, op->op.u.text.y, op->span);
2892 op->bb = bb0;
2893 expandBB (&bb, bb0.LL);
2894 expandBB (&bb, bb0.UR);
2895 if (!xd->freefunc)
2896 xd->freefunc = (freefunc_t)freePara;
2897 break;
2898 case xd_font :
2899 fontsize = op->op.u.font.size;
2900 fontname = op->op.u.font.name;
2901 break;
2902 case xd_fontchar :
2903 fontflags = op->op.u.fontchar;
2904 break;
2905 default :
2906 break;
2907 }
2908 op++;
2909 }
2910 return bb;
2911}
2912
2913static void init_gvc(GVC_t * gvc, graph_t * g)
2914{
2915 double xf, yf;
2916 char *p;
2917 int i;
2918
2919 gvc->g = g;
2920
2921 /* margins */
2922 gvc->graph_sets_margin = false;
2923 if ((p = agget(g, "margin"))) {
2924 i = sscanf(p, "%lf,%lf", &xf, &yf);
2925 if (i > 0) {
2926 gvc->margin.x = gvc->margin.y = xf * POINTS_PER_INCH;
2927 if (i > 1)
2928 gvc->margin.y = yf * POINTS_PER_INCH;
2929 gvc->graph_sets_margin = true;
2930 }
2931 }
2932
2933 /* pad */
2934 gvc->graph_sets_pad = false;
2935 if ((p = agget(g, "pad"))) {
2936 i = sscanf(p, "%lf,%lf", &xf, &yf);
2937 if (i > 0) {
2938 gvc->pad.x = gvc->pad.y = xf * POINTS_PER_INCH;
2939 if (i > 1)
2940 gvc->pad.y = yf * POINTS_PER_INCH;
2941 gvc->graph_sets_pad = true;
2942 }
2943 }
2944
2945 /* pagesize */
2946 gvc->graph_sets_pageSize = false;
2947 gvc->pageSize = GD_drawing(g)->page;
2948 if (GD_drawing(g)->page.x > 0.001 && GD_drawing(g)->page.y > 0.001)
2949 gvc->graph_sets_pageSize = true;
2950
2951 /* rotation */
2952 if (GD_drawing(g)->landscape)
2953 gvc->rotation = 90;
2954 else
2955 gvc->rotation = 0;
2956
2957 /* pagedir */
2958 gvc->pagedir = "BL";
2959 if ((p = agget(g, "pagedir")) && p[0])
2960 gvc->pagedir = p;
2961
2962
2963 /* bounding box */
2964 gvc->bb = GD_bb(g);
2965
2966 /* clusters have peripheries */
2967 G_peripheries = agfindgraphattr(g, "peripheries");
2968 G_penwidth = agfindgraphattr(g, "penwidth");
2969
2970 /* default font */
2975
2976 /* default line style */
2978
2979 gvc->graphname = agnameof(g);
2980}
2981
2982static void init_job_pad(GVJ_t *job)
2983{
2984 GVC_t *gvc = job->gvc;
2985
2986 if (gvc->graph_sets_pad) {
2987 job->pad = gvc->pad;
2988 }
2989 else {
2990 switch (job->output_lang) {
2991 case GVRENDER_PLUGIN:
2992 job->pad.x = job->pad.y = job->render.features->default_pad;
2993 break;
2994 default:
2995 job->pad.x = job->pad.y = DEFAULT_GRAPH_PAD;
2996 break;
2997 }
2998 }
2999}
3000
3001static void init_job_margin(GVJ_t *job)
3002{
3003 GVC_t *gvc = job->gvc;
3004
3005 if (gvc->graph_sets_margin) {
3006 job->margin = gvc->margin;
3007 }
3008 else {
3009 /* set default margins depending on format */
3010 switch (job->output_lang) {
3011 case GVRENDER_PLUGIN:
3012 job->margin = job->device.features->default_margin;
3013 break;
3014 case PCL: case MIF: case METAPOST: case VTX: case QPDF:
3015 job->margin.x = job->margin.y = DEFAULT_PRINT_MARGIN;
3016 break;
3017 default:
3018 job->margin.x = job->margin.y = DEFAULT_EMBED_MARGIN;
3019 break;
3020 }
3021 }
3022
3023}
3024
3025static void init_job_dpi(GVJ_t *job, graph_t *g)
3026{
3027 GVJ_t *firstjob = job->gvc->active_jobs;
3028
3029 if (GD_drawing(g)->dpi != 0) {
3030 job->dpi.x = job->dpi.y = GD_drawing(g)->dpi;
3031 }
3032 else if (firstjob && firstjob->device_sets_dpi) {
3033 job->dpi = firstjob->device_dpi; /* some devices set dpi in initialize() */
3034 }
3035 else {
3036 /* set default margins depending on format */
3037 switch (job->output_lang) {
3038 case GVRENDER_PLUGIN:
3039 job->dpi = job->device.features->default_dpi;
3040 break;
3041 default:
3042 job->dpi.x = job->dpi.y = DEFAULT_DPI;
3043 break;
3044 }
3045 }
3046}
3047
3048static void init_job_viewport(GVJ_t * job, graph_t * g)
3049{
3050 GVC_t *gvc = job->gvc;
3051 pointf LL, UR, size, sz;
3052 double X, Y, Z, x, y;
3053 int rv;
3054 Agnode_t *n;
3055 char *str, *nodename = NULL;
3056
3057 UR = gvc->bb.UR;
3058 LL = gvc->bb.LL;
3059 job->bb.LL.x = LL.x - job->pad.x; /* job->bb is bb of graph and padding - graph units */
3060 job->bb.LL.y = LL.y - job->pad.y;
3061 job->bb.UR.x = UR.x + job->pad.x;
3062 job->bb.UR.y = UR.y + job->pad.y;
3063 sz.x = job->bb.UR.x - job->bb.LL.x; /* size, including padding - graph units */
3064 sz.y = job->bb.UR.y - job->bb.LL.y;
3065
3066 /* determine final drawing size and scale to apply. */
3067 /* N.B. size given by user is not rotated by landscape mode */
3068 /* start with "natural" size of layout */
3069
3070 Z = 1.0;
3071 if (GD_drawing(g)->size.x > 0.001 && GD_drawing(g)->size.y > 0.001) { /* graph size was given by user... */
3072 size = GD_drawing(g)->size;
3073 if (sz.x <= 0.001) sz.x = size.x;
3074 if (sz.y <= 0.001) sz.y = size.y;
3075 if (size.x < sz.x || size.y < sz.y /* drawing is too big (in either axis) ... */
3076 || (GD_drawing(g)->filled /* or ratio=filled requested and ... */
3077 && size.x > sz.x && size.y > sz.y)) /* drawing is too small (in both axes) ... */
3078 Z = fmin(size.x / sz.x, size.y / sz.y);
3079 }
3080
3081 /* default focus, in graph units = center of bb */
3082 x = (LL.x + UR.x) / 2.;
3083 y = (LL.y + UR.y) / 2.;
3084
3085 /* rotate and scale bb to give default absolute size in points*/
3086 job->rotation = job->gvc->rotation;
3087 X = sz.x * Z;
3088 Y = sz.y * Z;
3089
3090 /* user can override */
3091 if ((str = agget(g, "viewport"))) {
3092 nodename = gv_alloc(strlen(str) + 1);
3093 rv = sscanf(str, "%lf,%lf,%lf,\'%[^\']\'", &X, &Y, &Z, nodename);
3094 if (rv == 4) {
3095 n = agfindnode(g->root, nodename);
3096 if (n) {
3097 x = ND_coord(n).x;
3098 y = ND_coord(n).y;
3099 }
3100 }
3101 else {
3102 char junk;
3103 rv = sscanf(str, "%lf,%lf,%lf,%[^,]%c", &X, &Y, &Z, nodename, &junk);
3104 if (rv == 4) {
3105 n = agfindnode(g->root, nodename);
3106 if (n) {
3107 x = ND_coord(n).x;
3108 y = ND_coord(n).y;
3109 }
3110 }
3111 else {
3112 rv = sscanf(str, "%lf,%lf,%lf,%lf,%lf", &X, &Y, &Z, &x, &y);
3113 }
3114 }
3115 free (nodename);
3116 }
3117 /* rv is ignored since args retain previous values if not scanned */
3118
3119 /* job->view gives port size in graph units, unscaled or rotated
3120 * job->zoom gives scaling factor.
3121 * job->focus gives the position in the graph of the center of the port
3122 */
3123 job->view.x = X;
3124 job->view.y = Y;
3125 job->zoom = Z; /* scaling factor */
3126 job->focus.x = x;
3127 job->focus.y = y;
3128}
3129
3130static void emit_cluster_colors(GVJ_t * job, graph_t * g)
3131{
3132 graph_t *sg;
3133 int c;
3134 char *str;
3135
3136 for (c = 1; c <= GD_n_cluster(g); c++) {
3137 sg = GD_clust(g)[c];
3138 emit_cluster_colors(job, sg);
3139 if (((str = agget(sg, "color")) != 0) && str[0])
3141 if (((str = agget(sg, "pencolor")) != 0) && str[0])
3143 if (((str = agget(sg, "bgcolor")) != 0) && str[0])
3145 if (((str = agget(sg, "fillcolor")) != 0) && str[0])
3147 if (((str = agget(sg, "fontcolor")) != 0) && str[0])
3149 }
3150}
3151
3152static void emit_colors(GVJ_t * job, graph_t * g)
3153{
3154 node_t *n;
3155 edge_t *e;
3156 char *str, *colors;
3157
3159 if (((str = agget(g, "bgcolor")) != 0) && str[0])
3161 if (((str = agget(g, "fontcolor")) != 0) && str[0])
3163
3164 emit_cluster_colors(job, g);
3165 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3166 if (((str = agget(n, "color")) != 0) && str[0])
3168 if (((str = agget(n, "pencolor")) != 0) && str[0])
3170 if (((str = agget(n, "fillcolor")) != 0) && str[0]) {
3171 if (strchr(str, ':')) {
3172 colors = gv_strdup(str);
3173 for (str = strtok(colors, ":"); str;
3174 str = strtok(0, ":")) {
3175 if (str[0])
3177 }
3178 free(colors);
3179 }
3180 else {
3182 }
3183 }
3184 if (((str = agget(n, "fontcolor")) != 0) && str[0])
3186 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3187 if (((str = agget(e, "color")) != 0) && str[0]) {
3188 if (strchr(str, ':')) {
3189 colors = gv_strdup(str);
3190 for (str = strtok(colors, ":"); str;
3191 str = strtok(0, ":")) {
3192 if (str[0])
3194 }
3195 free(colors);
3196 }
3197 else {
3199 }
3200 }
3201 if (((str = agget(e, "fontcolor")) != 0) && str[0])
3203 }
3204 }
3205}
3206
3207static void emit_view(GVJ_t * job, graph_t * g, int flags)
3208{
3209 GVC_t * gvc = job->gvc;
3210 node_t *n;
3211 edge_t *e;
3212
3213 gvc->common.viewNum++;
3214 /* when drawing, lay clusters down before nodes and edges */
3215 if (!(flags & EMIT_CLUSTERS_LAST))
3216 emit_clusters(job, g, flags);
3217 if (flags & EMIT_SORTED) {
3218 /* output all nodes, then all edges */
3220 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3221 emit_node(job, n);
3222 gvrender_end_nodes(job);
3224 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3225 for (e = agfstout(g, n); e; e = agnxtout(g, e))
3226 emit_edge(job, e);
3227 }
3228 gvrender_end_edges(job);
3229 } else if (flags & EMIT_EDGE_SORTED) {
3230 /* output all edges, then all nodes */
3232 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3233 for (e = agfstout(g, n); e; e = agnxtout(g, e))
3234 emit_edge(job, e);
3235 gvrender_end_edges(job);
3237 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3238 emit_node(job, n);
3239 gvrender_end_nodes(job);
3240 } else if (flags & EMIT_PREORDER) {
3242 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3243 if (write_node_test(g, n))
3244 emit_node(job, n);
3245 gvrender_end_nodes(job);
3247
3248 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3249 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3250 if (write_edge_test(g, e))
3251 emit_edge(job, e);
3252 }
3253 }
3254 gvrender_end_edges(job);
3255 } else {
3256 /* output in breadth first graph walk order */
3257 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3258 emit_node(job, n);
3259 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3260 emit_node(job, aghead(e));
3261 emit_edge(job, e);
3262 }
3263 }
3264 }
3265 /* when mapping, detect events on clusters after nodes and edges */
3267 emit_clusters(job, g, flags);
3268}
3269
3270static void emit_begin_graph(GVJ_t * job, graph_t * g)
3271{
3272 obj_state_t *obj;
3273
3274 obj = push_obj_state(job);
3275 obj->type = ROOTGRAPH_OBJTYPE;
3276 obj->u.g = g;
3277 obj->emit_state = EMIT_GDRAW;
3278
3279 initObjMapData (job, GD_label(g), g);
3280
3282}
3283
3284static void emit_end_graph(GVJ_t * job)
3285{
3286 gvrender_end_graph(job);
3287 pop_obj_state(job);
3288}
3289
3290#define NotFirstPage(j) (((j)->layerNum>1)||((j)->pagesArrayElem.x > 0)||((j)->pagesArrayElem.x > 0))
3291
3292static void emit_page(GVJ_t * job, graph_t * g)
3293{
3294 obj_state_t *obj = job->obj;
3295 int flags = job->flags;
3296 size_t nump = 0;
3297 textlabel_t *lab;
3298 pointf *p = NULL;
3299 char* saveid;
3300 agxbuf xb = {0};
3301
3302 /* For the first page, we can use the values generated in emit_begin_graph.
3303 * For multiple pages, we need to generate a new id.
3304 */
3305 if (NotFirstPage(job)) {
3306 saveid = obj->id;
3307 layerPagePrefix (job, &xb);
3308 agxbput (&xb, saveid);
3309 obj->id = agxbuse(&xb);
3310 }
3311 else
3312 saveid = NULL;
3313
3314 char *previous_color_scheme = setColorScheme(agget(g, "colorscheme"));
3315 setup_page(job);
3320 && (obj->url || obj->explicit_tooltip)) {
3324 nump = 2;
3325 }
3326 else {
3328 nump = 4;
3329 }
3330 p = gv_calloc(nump, sizeof(pointf));
3331 p[0] = job->pageBox.LL;
3332 p[1] = job->pageBox.UR;
3334 rect2poly(p);
3335 }
3337 gvrender_ptf_A(job, p, p, nump);
3338 obj->url_map_p = p;
3339 obj->url_map_n = nump;
3340 }
3341 if ((flags & GVRENDER_DOES_LABELS) && ((lab = GD_label(g))))
3342 /* do graph label on every page and rely on clipping to show it on the right one(s) */
3343 obj->label = lab->text;
3344 /* If EMIT_CLUSTERS_LAST is set, we assume any URL or tooltip
3345 * attached to the root graph is emitted either in begin_page
3346 * or end_page of renderer.
3347 */
3348 if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip)) {
3349 emit_map_rect(job, job->clip);
3350 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3351 }
3352 emit_background(job, g);
3353 if (GD_label(g))
3355 if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip))
3357 emit_view(job,g,flags);
3358 gvrender_end_page(job);
3359 if (saveid) {
3360 obj->id = saveid;
3361 }
3362 agxbfree(&xb);
3363
3364 char *color_scheme = setColorScheme(previous_color_scheme);
3365 free(color_scheme);
3366 free(previous_color_scheme);
3367}
3368
3369void emit_graph(GVJ_t * job, graph_t * g)
3370{
3371 node_t *n;
3372 char *s;
3373 int flags = job->flags;
3374 int* lp;
3375
3376 /* device dpi is now known */
3377 job->scale.x = job->zoom * job->dpi.x / POINTS_PER_INCH;
3378 job->scale.y = job->zoom * job->dpi.y / POINTS_PER_INCH;
3379
3380 job->devscale.x = job->dpi.x / POINTS_PER_INCH;
3381 job->devscale.y = job->dpi.y / POINTS_PER_INCH;
3382 if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
3383 job->devscale.y *= -1;
3384
3385 /* compute current view in graph units */
3386 if (job->rotation) {
3387 job->view.y = job->width / job->scale.y;
3388 job->view.x = job->height / job->scale.x;
3389 }
3390 else {
3391 job->view.x = job->width / job->scale.x;
3392 job->view.y = job->height / job->scale.y;
3393 }
3394
3395 s = late_string(g, agattr(g, AGRAPH, "comment", 0), "");
3396 gvrender_comment(job, s);
3397
3398 job->layerNum = 0;
3399 emit_begin_graph(job, g);
3400
3401 if (flags & EMIT_COLORS)
3402 emit_colors(job,g);
3403
3404 /* reset node state */
3405 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3406 ND_state(n) = 0;
3407 /* iterate layers */
3408 for (firstlayer(job,&lp); validlayer(job); nextlayer(job,&lp)) {
3409 if (numPhysicalLayers (job) > 1)
3411
3412 /* iterate pages */
3413 for (firstpage(job); validpage(job); nextpage(job))
3414 emit_page(job, g);
3415
3416 if (numPhysicalLayers (job) > 1)
3417 gvrender_end_layer(job);
3418 }
3419 emit_end_graph(job);
3420}
3421
3424 .link = -1, // link - allocate separate holder objects
3425 .freef = free,
3426};
3427
3428bool emit_once(char *str) {
3429 if (strings == 0)
3431 if (!dtsearch(strings, str)) {
3433 return true;
3434 }
3435 return false;
3436}
3437
3439{
3440 if (strings) {
3442 strings = 0;
3443 }
3444}
3445
3446static void emit_begin_cluster(GVJ_t * job, Agraph_t * sg)
3447{
3448 obj_state_t *obj;
3449
3450 obj = push_obj_state(job);
3451 obj->type = CLUSTER_OBJTYPE;
3452 obj->u.sg = sg;
3453 obj->emit_state = EMIT_CDRAW;
3454
3455 initObjMapData (job, GD_label(sg), sg);
3456
3458}
3459
3460static void emit_end_cluster(GVJ_t *job) {
3462 pop_obj_state(job);
3463}
3464
3465void emit_clusters(GVJ_t * job, Agraph_t * g, int flags)
3466{
3467 int doPerim, c, filled;
3468 pointf AF[4];
3469 char *color, *fillcolor, *pencolor, **style, *s;
3470 graph_t *sg;
3471 node_t *n;
3472 edge_t *e;
3473 obj_state_t *obj;
3474 textlabel_t *lab;
3475 int doAnchor;
3476 double penwidth;
3477
3478 for (c = 1; c <= GD_n_cluster(g); c++) {
3479 sg = GD_clust(g)[c];
3480 if (!clust_in_layer(job, sg))
3481 continue;
3482 /* when mapping, detect events on clusters after sub_clusters */
3484 emit_clusters(job, sg, flags);
3485 emit_begin_cluster(job, sg);
3486 obj = job->obj;
3487 doAnchor = obj->url || obj->explicit_tooltip;
3488 char *previous_color_scheme = setColorScheme(agget(sg, "colorscheme"));
3489 if (doAnchor && !(flags & EMIT_CLUSTERS_LAST)) {
3490 emit_map_rect(job, GD_bb(sg));
3491 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3492 }
3493 filled = 0;
3494 graphviz_polygon_style_t istyle = {0};
3495 if ((style = checkClusterStyle(sg, &istyle))) {
3496 gvrender_set_style(job, style);
3497 if (istyle.filled)
3498 filled = FILL;
3499 }
3500 fillcolor = pencolor = 0;
3501
3502 if (GD_gui_state(sg) & GUI_STATE_ACTIVE) {
3503 pencolor = DEFAULT_ACTIVEPENCOLOR;
3504 fillcolor = DEFAULT_ACTIVEFILLCOLOR;
3505 filled = FILL;
3506 }
3507 else if (GD_gui_state(sg) & GUI_STATE_SELECTED) {
3508 pencolor = DEFAULT_SELECTEDPENCOLOR;
3509 fillcolor = DEFAULT_SELECTEDFILLCOLOR;
3510 filled = FILL;
3511 }
3512 else if (GD_gui_state(sg) & GUI_STATE_DELETED) {
3513 pencolor = DEFAULT_DELETEDPENCOLOR;
3514 fillcolor = DEFAULT_DELETEDFILLCOLOR;
3515 filled = FILL;
3516 }
3517 else if (GD_gui_state(sg) & GUI_STATE_VISITED) {
3518 pencolor = DEFAULT_VISITEDPENCOLOR;
3519 fillcolor = DEFAULT_VISITEDFILLCOLOR;
3520 filled = FILL;
3521 }
3522 else {
3523 if ((color = agget(sg, "color")) != 0 && color[0])
3524 fillcolor = pencolor = color;
3525 if ((color = agget(sg, "pencolor")) != 0 && color[0])
3526 pencolor = color;
3527 if ((color = agget(sg, "fillcolor")) != 0 && color[0])
3528 fillcolor = color;
3529 /* bgcolor is supported for backward compatibility
3530 if fill is set, fillcolor trumps bgcolor, so
3531 don't bother checking.
3532 if gradient is set fillcolor trumps bgcolor
3533 */
3534 if ((filled == 0 || !fillcolor) && (color = agget(sg, "bgcolor")) != 0 && color[0]) {
3535 fillcolor = color;
3536 filled = FILL;
3537 }
3538
3539 }
3540 if (!pencolor) pencolor = DEFAULT_COLOR;
3541 if (!fillcolor) fillcolor = DEFAULT_FILL;
3542 char *clrs[2] = {0};
3543 if (filled != 0) {
3544 double frac;
3545 if (findStopColor (fillcolor, clrs, &frac)) {
3546 gvrender_set_fillcolor(job, clrs[0]);
3547 if (clrs[1])
3548 gvrender_set_gradient_vals(job,clrs[1],late_int(sg,G_gradientangle,0,0), frac);
3549 else
3551 if (istyle.radial)
3552 filled = RGRADIENT;
3553 else
3554 filled = GRADIENT;
3555 }
3556 else
3557 gvrender_set_fillcolor(job, fillcolor);
3558 }
3559
3560 if (G_penwidth && ((s=ag_xget(sg,G_penwidth)) && s[0])) {
3561 penwidth = late_double(sg, G_penwidth, 1.0, 0.0);
3563 }
3564
3565 if (istyle.rounded) {
3566 if ((doPerim = late_int(sg, G_peripheries, 1, 0)) || filled != 0) {
3567 AF[0] = GD_bb(sg).LL;
3568 AF[2] = GD_bb(sg).UR;
3569 AF[1].x = AF[2].x;
3570 AF[1].y = AF[0].y;
3571 AF[3].x = AF[0].x;
3572 AF[3].y = AF[2].y;
3573 if (doPerim)
3574 gvrender_set_pencolor(job, pencolor);
3575 else
3576 gvrender_set_pencolor(job, "transparent");
3577 round_corners(job, AF, 4, istyle, filled);
3578 }
3579 }
3580 else if (istyle.striped) {
3581 int rv;
3582 AF[0] = GD_bb(sg).LL;
3583 AF[2] = GD_bb(sg).UR;
3584 AF[1].x = AF[2].x;
3585 AF[1].y = AF[0].y;
3586 AF[3].x = AF[0].x;
3587 AF[3].y = AF[2].y;
3588 if (late_int(sg, G_peripheries, 1, 0) == 0)
3589 gvrender_set_pencolor(job, "transparent");
3590 else
3591 gvrender_set_pencolor(job, pencolor);
3592 rv = stripedBox (job, AF, fillcolor, 0);
3593 if (rv > 1)
3594 agerr (AGPREV, "in cluster %s\n", agnameof(sg));
3595 gvrender_box(job, GD_bb(sg), 0);
3596 }
3597 else {
3598 if (late_int(sg, G_peripheries, 1, 0)) {
3599 gvrender_set_pencolor(job, pencolor);
3600 gvrender_box(job, GD_bb(sg), filled);
3601 }
3602 else if (filled != 0) {
3603 gvrender_set_pencolor(job, "transparent");
3604 gvrender_box(job, GD_bb(sg), filled);
3605 }
3606 }
3607
3608 free (clrs[0]);
3609 free(clrs[1]);
3610 if ((lab = GD_label(sg)))
3611 emit_label(job, EMIT_CLABEL, lab);
3612
3613 if (doAnchor) {
3614 if (flags & EMIT_CLUSTERS_LAST) {
3615 emit_map_rect(job, GD_bb(sg));
3616 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3617 }
3619 }
3620
3621 if (flags & EMIT_PREORDER) {
3622 for (n = agfstnode(sg); n; n = agnxtnode(sg, n)) {
3623 emit_node(job, n);
3624 for (e = agfstout(sg, n); e; e = agnxtout(sg, e))
3625 emit_edge(job, e);
3626 }
3627 }
3628 emit_end_cluster(job);
3629 /* when drawing, lay down clusters before sub_clusters */
3630 if (!(flags & EMIT_CLUSTERS_LAST))
3631 emit_clusters(job, sg, flags);
3632
3633 char *color_scheme = setColorScheme(previous_color_scheme);
3634 free(color_scheme);
3635 free(previous_color_scheme);
3636 }
3637}
3638
3639static bool is_style_delim(int c)
3640{
3641 switch (c) {
3642 case '(':
3643 case ')':
3644 case ',':
3645 case '\0':
3646 return true;
3647 default:
3648 return false;
3649 }
3650}
3651
3652#define SID 1
3653
3658typedef struct {
3659 int type;
3660 const char *start;
3661 size_t size;
3662} token_t;
3663
3664static token_t style_token(char **s) {
3665 char *p = *s;
3666 int token;
3667
3668 while (gv_isspace(*p) || *p == ',')
3669 p++;
3670 const char *start = p;
3671 switch (*p) {
3672 case '\0':
3673 token = 0;
3674 break;
3675 case '(':
3676 case ')':
3677 token = *p++;
3678 break;
3679 default:
3680 token = SID;
3681 while (!is_style_delim(*p)) {
3682 p++;
3683 }
3684 }
3685 *s = p;
3686 assert(start <= p);
3687 size_t size = (size_t)(p - start);
3688 return (token_t){.type = token, .start = start, .size = size};
3689}
3690
3691#define FUNLIMIT 64
3692
3693/* This is one of the worst internal designs in graphviz.
3694 * The use of '\0' characters within strings seems cute but it
3695 * makes all of the standard functions useless if not dangerous.
3696 * Plus the function uses static memory for both the array and
3697 * the character buffer. One hopes all of the values are used
3698 * before the function is called again.
3699 */
3700char **parse_style(char *s)
3701{
3702 static char *parse[FUNLIMIT];
3703 size_t parse_offsets[sizeof(parse) / sizeof(parse[0])];
3704 size_t fun = 0;
3705 bool in_parens = false;
3706 char *p;
3707 static agxbuf ps_xb;
3708
3709 p = s;
3710 while (true) {
3711 token_t c = style_token(&p);
3712 if (c.type == 0) {
3713 break;
3714 }
3715 switch (c.type) {
3716 case '(':
3717 if (in_parens) {
3718 agerrorf("nesting not allowed in style: %s\n", s);
3719 parse[0] = NULL;
3720 return parse;
3721 }
3722 in_parens = true;
3723 break;
3724
3725 case ')':
3726 if (!in_parens) {
3727 agerrorf("unmatched ')' in style: %s\n", s);
3728 parse[0] = NULL;
3729 return parse;
3730 }
3731 in_parens = false;
3732 break;
3733
3734 default:
3735 if (!in_parens) {
3736 if (fun == FUNLIMIT - 1) {
3737 agwarningf("truncating style '%s'\n", s);
3738 parse[fun] = NULL;
3739 return parse;
3740 }
3741 agxbputc(&ps_xb, '\0'); /* terminate previous */
3742 parse_offsets[fun++] = agxblen(&ps_xb);
3743 }
3744 agxbput_n(&ps_xb, c.start, c.size);
3745 agxbputc(&ps_xb, '\0');
3746 }
3747 }
3748
3749 if (in_parens) {
3750 agerrorf("unmatched '(' in style: %s\n", s);
3751 parse[0] = NULL;
3752 return parse;
3753 }
3754
3755 char *base = agxbuse(&ps_xb); // add final '\0' to buffer
3756
3757 // construct list of style strings
3758 for (size_t i = 0; i < fun; ++i) {
3759 parse[i] = base + parse_offsets[i];
3760 }
3761 parse[fun] = NULL;
3762
3763 return parse;
3764}
3765
3767{
3768 pointf p, p1, p2;
3769 boxf bb;
3770
3771 assert(bz.size > 0);
3772 assert(bz.size % 3 == 1);
3773 bb.LL = bb.UR = bz.list[0];
3774 for (size_t i = 1; i < bz.size;) {
3775 /* take mid-point between two control points for bb calculation */
3776 p1=bz.list[i];
3777 i++;
3778 p2=bz.list[i];
3779 i++;
3780 p.x = ( p1.x + p2.x ) / 2;
3781 p.y = ( p1.y + p2.y ) / 2;
3782 expandbp(&bb, p);
3783
3784 p=bz.list[i];
3785 expandbp(&bb, p);
3786 i++;
3787 }
3788 return bb;
3789}
3790
3791static void init_splines_bb(splines *spl)
3792{
3793 bezier bz;
3794 boxf bb, b;
3795
3796 assert(spl->size > 0);
3797 bz = spl->list[0];
3798 bb = bezier_bb(bz);
3799 for (size_t i = 0; i < spl->size; i++) {
3800 if (i > 0) {
3801 bz = spl->list[i];
3802 b = bezier_bb(bz);
3803 EXPANDBB(&bb, b);
3804 }
3805 if (bz.sflag) {
3806 b = arrow_bb(bz.sp, bz.list[0], 1);
3807 EXPANDBB(&bb, b);
3808 }
3809 if (bz.eflag) {
3810 b = arrow_bb(bz.ep, bz.list[bz.size - 1], 1);
3811 EXPANDBB(&bb, b);
3812 }
3813 }
3814 spl->bb = bb;
3815}
3816
3817static void init_bb_edge(edge_t *e)
3818{
3819 splines *spl;
3820
3821 spl = ED_spl(e);
3822 if (spl)
3823 init_splines_bb(spl);
3824}
3825
3826static void init_bb_node(graph_t *g, node_t *n)
3827{
3828 edge_t *e;
3829
3830 ND_bb(n).LL.x = ND_coord(n).x - ND_lw(n);
3831 ND_bb(n).LL.y = ND_coord(n).y - ND_ht(n) / 2.;
3832 ND_bb(n).UR.x = ND_coord(n).x + ND_rw(n);
3833 ND_bb(n).UR.y = ND_coord(n).y + ND_ht(n) / 2.;
3834
3835 for (e = agfstout(g, n); e; e = agnxtout(g, e))
3836 init_bb_edge(e);
3837
3838 /* IDEA - could also save in the node the bb of the node and
3839 all of its outedges, then the scan time would be proportional
3840 to just the number of nodes for many graphs.
3841 Wouldn't work so well if the edges are sprawling all over the place
3842 because then the boxes would overlap a lot and require more tests,
3843 but perhaps that wouldn't add much to the cost before trying individual
3844 nodes and edges. */
3845}
3846
3847static void init_bb(graph_t *g)
3848{
3849 node_t *n;
3850
3851 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3852 init_bb_node(g, n);
3853}
3854
3856extern const size_t gvevent_key_binding_size;
3858
3859/* Set LC_NUMERIC to "C" to get expected interpretation of %f
3860 * in printf functions. Languages like postscript and dot expect
3861 * floating point numbers to use a decimal point.
3862 *
3863 * If set is non-zero, the "C" locale set;
3864 * if set is zero, the original locale is reset.
3865 * Calls to the function can nest.
3866 */
3867void gv_fixLocale (int set)
3868{
3869 static char* save_locale;
3870 static int cnt;
3871
3872 if (set) {
3873 cnt++;
3874 if (cnt == 1) {
3875 save_locale = gv_strdup(setlocale (LC_NUMERIC, NULL));
3876 setlocale (LC_NUMERIC, "C");
3877 }
3878 }
3879 else if (cnt > 0) {
3880 cnt--;
3881 if (cnt == 0) {
3882 setlocale (LC_NUMERIC, save_locale);
3883 free (save_locale);
3884 }
3885 }
3886}
3887
3888
3889#define FINISH() if (Verbose) fprintf(stderr,"gvRenderJobs %s: %.2f secs.\n", agnameof(g), elapsed_sec())
3890
3892{
3893 static GVJ_t *prevjob;
3894 GVJ_t *job, *firstjob;
3895
3896 if (Verbose)
3897 start_timer();
3898
3899 if (!LAYOUT_DONE(g)) {
3900 agerrorf("Layout was not done. Missing layout plugins? \n");
3901 FINISH();
3902 return -1;
3903 }
3904
3905 init_bb(g);
3906 init_gvc(gvc, g);
3907 init_layering(gvc, g);
3908
3909 gv_fixLocale (1);
3910 for (job = gvjobs_first(gvc); job; job = gvjobs_next(gvc)) {
3911 if (gvc->gvg) {
3913 job->graph_index = gvc->gvg->graph_index;
3914 }
3915 else {
3916 job->input_filename = NULL;
3917 job->graph_index = 0;
3918 }
3919 job->common = &(gvc->common);
3920 job->layout_type = gvc->layout.type;
3923 if (!GD_drawing(g)) {
3924 agerrorf("layout was not done\n");
3925 gv_fixLocale (0);
3926 FINISH();
3927 return -1;
3928 }
3929
3931 if (job->output_lang == NO_SUPPORT) {
3932 agerrorf("renderer for %s is unavailable\n", job->output_langname);
3933 gv_fixLocale (0);
3934 FINISH();
3935 return -1;
3936 }
3937
3938 switch (job->output_lang) {
3939 case VTX:
3940 /* output sorted, i.e. all nodes then all edges */
3941 job->flags |= EMIT_SORTED;
3942 break;
3943 default:
3944 job->flags |= chkOrder(g);
3945 break;
3946 }
3947
3948 // if we already have an active job list and the device doesn't support
3949 // multiple output files, or we are about to write to a different output
3950 // device
3951 firstjob = gvc->active_jobs;
3952 if (firstjob) {
3953 if (! (firstjob->flags & GVDEVICE_DOES_PAGES)
3954 || strcmp(job->output_langname, firstjob->output_langname)) {
3955
3956 gvrender_end_job(firstjob);
3957
3958 gvc->active_jobs = NULL; /* clear active list */
3959 gvc->common.viewNum = 0;
3960 prevjob = NULL;
3961 }
3962 }
3963 else {
3964 prevjob = NULL;
3965 }
3966
3967 if (prevjob) {
3968 prevjob->next_active = job; /* insert job in active list */
3969 job->output_file = prevjob->output_file; /* FIXME - this is dumb ! */
3970 }
3971 else {
3972 if (gvrender_begin_job(job))
3973 continue;
3974 gvc->active_jobs = job; /* first job of new list */
3975 }
3976 job->next_active = NULL; /* terminate active list */
3978
3979 init_job_pad(job);
3980 init_job_margin(job);
3981 init_job_dpi(job, g);
3982 init_job_viewport(job, g);
3983 init_job_pagination(job, g);
3984
3985 if (! (job->flags & GVDEVICE_EVENTS)) {
3986#ifdef DEBUG
3987 /* Show_boxes is not defined, if at all,
3988 * until splines are generated in dot
3989 */
3990 show_boxes_append(&Show_boxes, NULL);
3991 show_boxes_sync(&Show_boxes);
3992 job->common->show_boxes = show_boxes_front(&Show_boxes);
3993#endif
3994 emit_graph(job, g);
3995 }
3996
3997 /* the last job, after all input graphs are processed,
3998 * is finalized from gvFinalize()
3999 */
4000 prevjob = job;
4001 }
4002 gv_fixLocale (0);
4003 FINISH();
4004 return 0;
4005}
4006
4007/* Check for colon in colorlist. If one exists, and not the first
4008 * character, store the characters before the colon in clrs[0] and
4009 * the characters after the colon (and before the next or end-of-string)
4010 * in clrs[1]. If there are no characters after the first colon, clrs[1]
4011 * is NULL. Return TRUE.
4012 * If there is no non-trivial string before a first colon, set clrs[0] to
4013 * NULL and return FALSE.
4014 *
4015 * Note that memory for clrs must be freed by calling function.
4016 */
4017bool findStopColor(const char *colorlist, char *clrs[2], double *frac) {
4018 colorsegs_t segs = {0};
4019 int rv;
4020 clrs[0] = NULL;
4021 clrs[1] = NULL;
4022
4023 rv = parseSegs(colorlist, &segs);
4024 if (rv || colorsegs_size(&segs) < 2 || colorsegs_front(&segs)->color == NULL) {
4025 colorsegs_free(&segs);
4026 return false;
4027 }
4028
4029 if (colorsegs_size(&segs) > 2)
4030 agwarningf("More than 2 colors specified for a gradient - ignoring remaining\n");
4031
4032 clrs[0] = gv_strdup(colorsegs_front(&segs)->color);
4033 if (colorsegs_get(&segs, 1).color) {
4034 clrs[1] = gv_strdup(colorsegs_get(&segs, 1).color);
4035 }
4036
4037 if (colorsegs_front(&segs)->hasFraction)
4038 *frac = colorsegs_front(&segs)->t;
4039 else if (colorsegs_get(&segs, 1).hasFraction)
4040 *frac = 1 - colorsegs_get(&segs, 1).t;
4041 else
4042 *frac = 0;
4043
4044 colorsegs_free(&segs);
4045 return true;
4046}
4047
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:170
char * late_nnstring(void *obj, attrsym_t *attr, char *defaultValue)
Definition utils.c:86
bool mapbool(const char *p)
Definition utils.c:337
char * late_string(void *obj, attrsym_t *attr, char *defaultValue)
Definition utils.c:80
int late_int(void *obj, attrsym_t *attr, int defaultValue, int minimum)
Definition utils.c:35
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:50
pointf dotneato_closest(splines *spl, pointf pt)
Definition utils.c:342
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:1194
#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:3428
static bool node_in_box(node_t *n, boxf b)
Definition emit.c:1693
static bool write_edge_test(Agraph_t *g, Agedge_t *e)
Definition emit.c:1369
static void free_stroke(stroke_t sp)
Definition emit.c:2113
static void map_point(GVJ_t *job, pointf pf)
Definition emit.c:334
static radfunc_t taperfun(edge_t *e)
Definition emit.c:2145
static void emit_xdot(GVJ_t *job, xdot *xd)
Definition emit.c:1404
static void emit_end_graph(GVJ_t *job)
Definition emit.c:3284
static void emit_end_cluster(GVJ_t *job)
Definition emit.c:3460
static int * parse_layerselect(GVC_t *gvc, char *p)
Definition emit.c:1049
static void emit_edge_graphics(GVJ_t *job, edge_t *e, char **styles)
Definition emit.c:2157
void emit_clusters(GVJ_t *job, Agraph_t *g, int flags)
Definition emit.c:3465
static Dict_t * strings
Definition emit.c:3422
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:3130
static double revfunc(double curlen, double totallen, double initwid)
Definition emit.c:2124
static void nodeIntersect(GVJ_t *job, pointf p, bool explicit_iurl, char *iurl, bool explicit_itooltip)
Definition emit.c:2638
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:3791
static bool selectedLayer(GVC_t *gvc, int layerNum, int numLayers, char *spec)
Definition emit.c:992
void gv_fixLocale(int set)
Definition emit.c:3867
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:1193
static void layerPagePrefix(GVJ_t *job, agxbuf *xb)
Definition emit.c:189
static void init_bb(graph_t *g)
Definition emit.c:3847
static void emit_end_node(GVJ_t *job)
Definition emit.c:1842
static pointf computeoffset_qr(pointf p, pointf q, pointf r, pointf s, double d)
Definition emit.c:1906
struct segitem_s segitem_t
static bool edge_in_layer(GVJ_t *job, edge_t *e)
Definition emit.c:1655
static double forfunc(double curlen, double totallen, double initwid)
Definition emit.c:2119
#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:1524
static void init_bb_edge(edge_t *e)
Definition emit.c:3817
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:1077
static char * interpretCRNL(char *ins)
Definition emit.c:251
static boxf bezier_bb(bezier bz)
Definition emit.c:3766
static bool is_style_delim(int c)
Definition emit.c:3639
static void expandBB(boxf *bb, pointf p)
Definition emit.c:2774
bool findStopColor(const char *colorlist, char *clrs[2], double *frac)
Definition emit.c:4017
static bool clust_in_layer(GVJ_t *job, graph_t *sg)
Definition emit.c:1675
static bool isRect(polygon_t *p)
Definition emit.c:689
static void emit_edge(GVJ_t *job, edge_t *e)
Definition emit.c:2728
static const char adjust[]
Definition emit.c:2771
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:3423
static void init_bb_node(graph_t *g, node_t *n)
Definition emit.c:3826
#define NotFirstPage(j)
Definition emit.c:3290
static double bothfunc(double curlen, double totallen, double initwid)
Definition emit.c:2137
static int multicolor(GVJ_t *job, edge_t *e, char **styles, const char *colors, double arrowsize, double penwidth)
Definition emit.c:2035
#define AEQ0(x)
Definition emit.c:444
#define FUNLIMIT
Definition emit.c:3691
static void emit_view(GVJ_t *job, graph_t *g, int flags)
Definition emit.c:3207
#define SID
Definition emit.c:3652
#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:2805
static void emit_begin_node(GVJ_t *job, node_t *n)
Definition emit.c:1700
static double approxLen(pointf *pts)
Definition emit.c:1965
static int numPhysicalLayers(GVJ_t *job)
Return number of physical layers to be emitted.
Definition emit.c:1153
static void init_job_dpi(GVJ_t *job, graph_t *g)
Definition emit.c:3025
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:1633
static void firstpage(GVJ_t *job)
Definition emit.c:1344
static void splitBSpline(bezier *bz, double t, bezier *left, bezier *right)
Definition emit.c:1981
static char * saved_color_scheme
Definition emit.c:1698
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:2579
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:3001
static void freePara(exdot_op *op)
Definition emit.c:2830
static bool validpage(GVJ_t *job)
Definition emit.c:1349
static int chkOrder(graph_t *g)
Definition emit.c:1119
static void init_job_viewport(GVJ_t *job, graph_t *g)
Definition emit.c:3048
static void emit_colors(GVJ_t *job, graph_t *g)
Definition emit.c:3152
#define SEP
static void emit_begin_cluster(GVJ_t *job, Agraph_t *sg)
Definition emit.c:3446
static void setup_page(GVJ_t *job)
Definition emit.c:1580
static int layer_index(GVC_t *gvc, char *str, int all)
Definition emit.c:977
static bool validlayer(GVJ_t *job)
Definition emit.c:1188
static void init_gvc(GVC_t *gvc, graph_t *g)
Definition emit.c:2913
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:1927
#define FINISH()
Definition emit.c:3889
static void emit_page(GVJ_t *job, graph_t *g)
Definition emit.c:3292
gvevent_key_binding_t gvevent_key_binding[]
Definition gvevent.c:521
char ** parse_style(char *s)
Definition emit.c:3700
static void firstlayer(GVJ_t *job, int **listp)
Definition emit.c:1163
static char * default_pencolor(agxbuf *buf, const char *pencolor, const char *deflt)
Definition emit.c:1955
void emit_once_reset(void)
Definition emit.c:3438
void emit_map_rect(GVJ_t *job, boxf b)
Definition emit.c:634
static void emit_end_edge(GVJ_t *job)
Definition emit.c:2661
static void init_layering(GVC_t *gvc, graph_t *g)
Definition emit.c:1131
static void init_job_pagination(GVJ_t *job, graph_t *g)
Definition emit.c:1229
static double nonefunc(double curlen, double totallen, double initwid)
Definition emit.c:2129
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:2782
void pop_obj_state(GVJ_t *job)
Definition emit.c:122
static void nextpage(GVJ_t *job)
Definition emit.c:1357
static pointf * copyPts(xdot_point *inpts, size_t numpts)
Definition emit.c:1395
static point pagecode(GVJ_t *job, char c)
Definition emit.c:1204
#define MARK_FIRST_SEG(L)
Definition emit.c:802
static bool edge_in_box(edge_t *e, boxf b)
Definition emit.c:2382
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:2402
static token_t style_token(char **s)
Definition emit.c:3664
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:3270
static bool selectedlayer(GVJ_t *job, char *spec)
Definition emit.c:1029
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:1382
static void emit_node(GVJ_t *job, node_t *n)
Definition emit.c:1854
#define P2RECT(p, pr, sx, sy)
Definition emit.c:48
void emit_graph(GVJ_t *job, graph_t *g)
Definition emit.c:3369
boxf xdotBB(Agraph_t *g)
Definition emit.c:2836
double(* radfunc_t)(double, double, double)
Definition emit.c:2117
static void init_job_pad(GVJ_t *job)
Definition emit.c:2982
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:1893
expr procedure type
Definition exparse.y:206
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:62
struct pointf_s pointf
#define DIST(p, q)
Definition geom.h:56
#define POINTS_PER_INCH
Definition geom.h:58
geometric functions (e.g. on points and boxes)
static pointf mid_pointf(pointf p, pointf q)
Definition geomprocs.h:106
static void expandbp(boxf *b, pointf p)
expand box b as needed to enclose point p
Definition geomprocs.h:44
static pointf sub_pointf(pointf p, pointf q)
Definition geomprocs.h:97
static point exch_xy(point p)
Definition geomprocs.h:124
static pointf exch_xyf(pointf p)
Definition geomprocs.h:133
static bool boxf_overlap(boxf b0, boxf b1)
Definition geomprocs.h:142
#define EXPANDBB(b0, b1)
Definition geomprocs.h:64
Agsym_t * N_fontsize
Definition globals.h:75
Agsym_t * N_layer
Definition globals.h:78
Agsym_t * E_comment
Definition globals.h:86
Agsym_t * G_peripheries
Definition globals.h:71
Agsym_t * E_decorate
Definition globals.h:84
Agsym_t * E_style
Definition globals.h:84
Agsym_t * N_fontname
Definition globals.h:75
Agsym_t * N_comment
Definition globals.h:79
Agsym_t * N_style
Definition globals.h:76
Agsym_t * E_fillcolor
Definition globals.h:82
Agsym_t * E_dir
Definition globals.h:84
show_boxes_t Show_boxes
Definition globals.h:57
Agsym_t * E_color
Definition globals.h:82
Agsym_t * E_arrowsz
Definition globals.h:85
Agsym_t * G_gradientangle
Definition globals.h:72
Agsym_t * G_penwidth
Definition globals.h:71
bool Y_invert
invert y in dot & plain output
Definition globals.h:67
Agsym_t * E_penwidth
Definition globals.h:92
Agsym_t * E_layer
Definition globals.h:85
static double len(glCompPoint p)
Definition glutils.c: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:200
Agsym_t * agattr(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up attributes of a graph
Definition attr.c:371
char * agget(void *obj, char *name)
Definition attr.c:465
char * agxget(void *obj, Agsym_t *sym)
Definition attr.c:475
#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:883
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:884
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:852
#define agfindgraphattr(g, a)
Definition types.h:613
#define GD_drawing(g)
Definition types.h:353
int agisdirected(Agraph_t *g)
Definition graph.c:180
#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:143
int agcontains(Agraph_t *, void *obj)
returns non-zero if obj is a member of (sub)graph
Definition obj.c:233
int agobjkind(void *obj)
Definition obj.c:252
Agraph_t * agroot(void *obj)
Definition obj.c:168
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
#define LAYOUT_DONE(g)
Definition gvc.h:52
int gvRenderJobs(GVC_t *gvc, graph_t *g)
Definition emit.c:3891
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:63
#define SWAP(a, b)
Definition gv_math.h:130
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:30
#define DEFINE_LIST(name, type)
Definition list.h:22
static int * ps
Definition lu.c:51
static void add_point(int *n, int igrp, double **x, int *nmax, double point[], int **groups)
Definition make_map.c: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:152
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:424
Agraph_t * root
subgraphs - ancestors
Definition cgraph.h:433
const char ** show_boxes
emit code for correct box coordinates
Definition gvcommon.h:25
int viewNum
rendering state
Definition gvcommon.h:29
Definition gvcint.h: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:158
xdot_font font
Definition xdot.h:157
xdot_polyline polygon
Definition xdot.h:150
xdot_rect ellipse
Definition xdot.h:149
char * color
Definition xdot.h:155
unsigned int fontchar
Definition xdot.h:159
xdot_kind kind
Definition xdot.h:147
xdot_text text
Definition xdot.h:153
xdot_polyline bezier
Definition xdot.h:152
xdot_polyline polyline
Definition xdot.h:151
xdot_color grad_color
Definition xdot.h:156
union _xdot_op::@126 u
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:3660
int type
Token category.
Definition emit.c:3659
size_t size
Number of bytes in the token content.
Definition emit.c:3661
xdot_grad_type type
Definition xdot.h:68
xdot_linear_grad ling
Definition xdot.h:71
xdot_radial_grad ring
Definition xdot.h:72
union xdot_color::@125 u
double size
Definition xdot.h:104
char * name
Definition xdot.h:105
double x
Definition xdot.h:79
double y
Definition xdot.h:79
size_t cnt
Definition xdot.h:87
xdot_point * pts
Definition xdot.h:88
double x
Definition xdot.h:83
double w
Definition xdot.h:83
double y
Definition xdot.h:83
double h
Definition xdot.h:83
size_t n_polygon_pts
Definition xdot.h:178
size_t cnt
Definition xdot.h:175
size_t n_text
Definition xdot.h:183
size_t n_polyline_pts
Definition xdot.h:180
size_t n_bezier
Definition xdot.h:181
size_t n_polygon
Definition xdot.h:177
size_t n_polyline
Definition xdot.h:179
size_t n_ellipse
Definition xdot.h:176
size_t n_bezier_pts
Definition xdot.h:182
char * text
Definition xdot.h:95
double x
Definition xdot.h:92
xdot_align align
Definition xdot.h:93
double y
Definition xdot.h:92
Definition xdot.h:166
Non-owning string references.
static char * strview_str(strview_t source)
make a heap-allocated string from this string view
Definition strview.h:41
double elapsed_sec(void)
Definition timing.c:48
void start_timer(void)
Definition timing.c:43
String tokenization.
static strview_t tok_get(const tok_t *t)
get the current token
Definition tokenize.h:76
static tok_t tok(const char *input, const char *separators)
begin tokenization of a new string
Definition tokenize.h:43
static bool tok_end(const tok_t *t)
is this tokenizer exhausted?
Definition tokenize.h:68
static void tok_next(tok_t *t)
advance to the next token in the string being scanned
Definition tokenize.h:85
#define ag_xget(x, a)
Definition types.h:606
#define GUI_STATE_ACTIVE
Definition types.h:256
@ SH_POINT
Definition types.h:187
@ SH_POLY
Definition types.h:187
#define GUI_STATE_SELECTED
Definition types.h:257
#define GUI_STATE_DELETED
Definition types.h:259
#define GUI_STATE_VISITED
Definition types.h:258
Definition grammar.c: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:396
int statXDot(xdot *x, xdot_stats *sp)
Definition xdot.c:778
xdot * parseXDotF(char *s, drawfunc_t fns[], size_t sz)
Definition xdot.c:390
parsing and deparsing of xdot operations
@ xd_radial
Definition xdot.h:46
void(* freefunc_t)(xdot_op *)
Definition xdot.h:144
@ xd_filled_polygon
Definition xdot.h:111
@ xd_pen_color
Definition xdot.h:118
@ xd_unfilled_ellipse
Definition xdot.h:110
@ xd_fontchar
Definition xdot.h:124
@ xd_font
Definition xdot.h:119
@ xd_fill_color
Definition xdot.h:117
@ xd_unfilled_bezier
Definition xdot.h:114
@ xd_grad_fill_color
Definition xdot.h:122
@ xd_polyline
Definition xdot.h:115
@ xd_text
Definition xdot.h:116
@ xd_filled_ellipse
Definition xdot.h:109
@ xd_image
Definition xdot.h:121
@ xd_unfilled_polygon
Definition xdot.h:112
@ xd_grad_pen_color
Definition xdot.h:123
@ xd_filled_bezier
Definition xdot.h:113
@ xd_style
Definition xdot.h:120