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