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