Graphviz 14.1.2~dev.20251230.1146
Loading...
Searching...
No Matches
emit.c
Go to the documentation of this file.
1
7/*************************************************************************
8 * Copyright (c) 2011 AT&T Intellectual Property
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * which accompanies this distribution, and is available at
12 * https://www.eclipse.org/legal/epl-v10.html
13 *
14 * Contributors: Details at https://graphviz.org
15 *************************************************************************/
16
17#include "config.h"
18#include <assert.h>
19#include <float.h>
20#include <stdatomic.h>
21#include <stdbool.h>
22#include <stddef.h>
23#include <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 if (abs(job->pagesArrayMajor.x + job->pagesArrayMinor.x) != 1
1259 || abs(job->pagesArrayMajor.y + job->pagesArrayMinor.y) != 1) {
1260 job->pagesArrayMajor = pagecode(job, 'B');
1261 job->pagesArrayMinor = pagecode(job, 'L');
1262 agwarningf("pagedir=%s ignored\n", gvc->pagedir);
1263 }
1264
1265 /* determine page box including centering */
1266 if (GD_drawing(g)->centered) {
1267 if (pageSize.x > imageSize.x)
1268 centering.x = (pageSize.x - imageSize.x) / 2;
1269 if (pageSize.y > imageSize.y)
1270 centering.y = (pageSize.y - imageSize.y) / 2;
1271 }
1272
1273 /* rotate back into graph orientation */
1274 if (job->rotation) {
1275 imageSize = exch_xyf(imageSize);
1276 margin = exch_xyf(margin);
1277 centering = exch_xyf(centering);
1278 }
1279
1280 /* canvas area, centered if necessary */
1281 job->canvasBox.LL = add_pointf(margin, centering);
1282 job->canvasBox.UR = add_pointf(add_pointf(margin, centering), imageSize);
1283
1284 /* size of one page in graph units */
1285 job->pageSize.x = imageSize.x / job->zoom;
1286 job->pageSize.y = imageSize.y / job->zoom;
1287
1288 /* pageBoundingBox in device units and page orientation */
1289 job->pageBoundingBox.LL.x = ROUND(job->canvasBox.LL.x * job->dpi.x / POINTS_PER_INCH);
1290 job->pageBoundingBox.LL.y = ROUND(job->canvasBox.LL.y * job->dpi.y / POINTS_PER_INCH);
1291 job->pageBoundingBox.UR.x = ROUND(job->canvasBox.UR.x * job->dpi.x / POINTS_PER_INCH);
1292 job->pageBoundingBox.UR.y = ROUND(job->canvasBox.UR.y * job->dpi.y / POINTS_PER_INCH);
1293 if (job->rotation) {
1296 job->canvasBox.LL = exch_xyf(job->canvasBox.LL);
1297 job->canvasBox.UR = exch_xyf(job->canvasBox.UR);
1298 }
1299}
1300
1301static void firstpage(GVJ_t *job)
1302{
1303 job->pagesArrayElem = job->pagesArrayFirst;
1304}
1305
1306static bool validpage(GVJ_t *job)
1307{
1308 return job->pagesArrayElem.x >= 0
1309 && job->pagesArrayElem.x < job->pagesArraySize.x
1310 && job->pagesArrayElem.y >= 0
1311 && job->pagesArrayElem.y < job->pagesArraySize.y;
1312}
1313
1314static void nextpage(GVJ_t *job)
1315{
1317 if (!validpage(job)) {
1318 if (job->pagesArrayMajor.y)
1319 job->pagesArrayElem.x = job->pagesArrayFirst.x;
1320 else
1321 job->pagesArrayElem.y = job->pagesArrayFirst.y;
1323 }
1324}
1325
1326static bool write_edge_test(Agraph_t * g, Agedge_t * e)
1327{
1328 Agraph_t *sg;
1329 int c;
1330
1331 for (c = 1; c <= GD_n_cluster(g); c++) {
1332 sg = GD_clust(g)[c];
1333 if (agcontains(sg, e))
1334 return false;
1335 }
1336 return true;
1337}
1338
1339static bool write_node_test(Agraph_t * g, Agnode_t * n)
1340{
1341 Agraph_t *sg;
1342 int c;
1343
1344 for (c = 1; c <= GD_n_cluster(g); c++) {
1345 sg = GD_clust(g)[c];
1346 if (agcontains(sg, n))
1347 return false;
1348 }
1349 return true;
1350}
1351
1352static pointf *copyPts(xdot_point *inpts, size_t numpts) {
1353 pointf *pts = gv_calloc(numpts, sizeof(pointf));
1354 for (size_t i = 0; i < numpts; i++) {
1355 pts[i].x = inpts[i].x;
1356 pts[i].y = inpts[i].y;
1357 }
1358 return pts;
1359}
1360
1361static void emit_xdot (GVJ_t * job, xdot* xd)
1362{
1363 int image_warn = 1;
1364 int angle;
1365 char** styles = NULL;
1366 int filled = FILL;
1367
1368 exdot_op *op = (exdot_op*)xd->ops;
1369 for (size_t i = 0; i < xd->cnt; i++) {
1370 switch (op->op.kind) {
1371 case xd_filled_ellipse :
1372 case xd_unfilled_ellipse :
1373 if (boxf_overlap(op->bb, job->clip)) {
1374 pointf pts[] = {{.x = op->op.u.ellipse.x - op->op.u.ellipse.w,
1375 .y = op->op.u.ellipse.y - op->op.u.ellipse.h},
1376 {.x = op->op.u.ellipse.x + op->op.u.ellipse.w,
1377 .y = op->op.u.ellipse.y + op->op.u.ellipse.h}};
1378 gvrender_ellipse(job, pts, op->op.kind == xd_filled_ellipse ? filled : 0);
1379 }
1380 break;
1381 case xd_filled_polygon :
1382 case xd_unfilled_polygon :
1383 if (boxf_overlap(op->bb, job->clip)) {
1384 pointf *pts = copyPts(op->op.u.polygon.pts, op->op.u.polygon.cnt);
1385 assert(op->op.u.polygon.cnt <= INT_MAX &&
1386 "polygon count exceeds gvrender_polygon support");
1387 gvrender_polygon(job, pts, op->op.u.polygon.cnt,
1388 op->op.kind == xd_filled_polygon ? filled : 0);
1389 free(pts);
1390 }
1391 break;
1392 case xd_filled_bezier :
1393 case xd_unfilled_bezier :
1394 if (boxf_overlap(op->bb, job->clip)) {
1395 pointf *pts = copyPts(op->op.u.bezier.pts, op->op.u.bezier.cnt);
1396 gvrender_beziercurve(job, pts, op->op.u.bezier.cnt,
1397 op->op.kind == xd_filled_bezier ? filled : 0);
1398 free(pts);
1399 }
1400 break;
1401 case xd_polyline :
1402 if (boxf_overlap(op->bb, job->clip)) {
1403 pointf *pts = copyPts(op->op.u.polyline.pts, op->op.u.polyline.cnt);
1404 gvrender_polyline(job, pts, op->op.u.polyline.cnt);
1405 free(pts);
1406 }
1407 break;
1408 case xd_text :
1409 if (boxf_overlap(op->bb, job->clip)) {
1410 pointf pt = {.x = op->op.u.text.x, .y = op->op.u.text.y};
1411 gvrender_textspan(job, pt, op->span);
1412 }
1413 break;
1414 case xd_fill_color :
1415 gvrender_set_fillcolor(job, op->op.u.color);
1416 filled = FILL;
1417 break;
1418 case xd_pen_color :
1419 gvrender_set_pencolor(job, op->op.u.color);
1420 filled = FILL;
1421 break;
1422 case xd_grad_fill_color :
1423 if (op->op.u.grad_color.type == xd_radial) {
1425 char *const clr0 = p->stops[0].color;
1426 char *const clr1 = p->stops[1].color;
1427 const double frac = p->stops[1].frac;
1428 if (p->x1 == p->x0 && p->y1 == p->y0) {
1429 angle = 0;
1430 } else {
1431 angle = (int)(180 * acos((p->x0 - p->x1) / p->r0) / M_PI);
1432 }
1433 gvrender_set_fillcolor(job, clr0);
1434 gvrender_set_gradient_vals(job, clr1, angle, frac);
1435 filled = RGRADIENT;
1436 } else {
1438 char *const clr0 = p->stops[0].color;
1439 char *const clr1 = p->stops[1].color;
1440 const double frac = p->stops[1].frac;
1441 angle = (int)(180 * atan2(p->y1 - p->y0, p->x1 - p->x0) / M_PI);
1442 gvrender_set_fillcolor(job, clr0);
1443 gvrender_set_gradient_vals(job, clr1, angle, frac);
1444 filled = GRADIENT;
1445 }
1446 break;
1447 case xd_grad_pen_color :
1448 agwarningf("gradient pen colors not yet supported.\n");
1449 break;
1450 case xd_font :
1451 /* fontsize and fontname already encoded via xdotBB */
1452 break;
1453 case xd_style :
1454 styles = parse_style (op->op.u.style);
1455 gvrender_set_style (job, styles);
1456 break;
1457 case xd_fontchar :
1458 /* font characteristics already encoded via xdotBB */
1459 break;
1460 case xd_image :
1461 if (image_warn) {
1462 agwarningf("Images unsupported in \"background\" attribute\n");
1463 image_warn = 0;
1464 }
1465 break;
1466 default:
1467 UNREACHABLE();
1468 }
1469 op++;
1470 }
1471 if (styles)
1473}
1474
1475static void emit_background(GVJ_t * job, graph_t *g)
1476{
1477 xdot* xd;
1478 char *str;
1479 int dfltColor;
1480
1481 /* if no bgcolor specified - first assume default of "white" */
1482 if (! ((str = agget(g, "bgcolor")) && str[0])) {
1483 str = "white";
1484 dfltColor = 1;
1485 }
1486 else
1487 dfltColor = 0;
1488
1489
1490 /* if device has no truecolor support, change "transparent" to "white" */
1491 if (! (job->flags & GVDEVICE_DOES_TRUECOLOR) && (streq(str, "transparent"))) {
1492 str = "white";
1493 dfltColor = 1;
1494 }
1495
1496 /* except for "transparent" on truecolor, or default "white" on (assumed) white paper, paint background */
1497 if (!( ((job->flags & GVDEVICE_DOES_TRUECOLOR) && streq(str, "transparent"))
1498 || ((job->flags & GVRENDER_NO_WHITE_BG) && dfltColor))) {
1499 char *clrs[2] = {0};
1500 double frac;
1501
1502 if ((findStopColor (str, clrs, &frac))) {
1503 int filled;
1504 graphviz_polygon_style_t istyle = {0};
1505 gvrender_set_fillcolor(job, clrs[0]);
1506 gvrender_set_pencolor(job, "transparent");
1507 checkClusterStyle(g, &istyle);
1508 if (clrs[1])
1509 gvrender_set_gradient_vals(job,clrs[1],late_int(g,G_gradientangle,0,0), frac);
1510 else
1512 if (istyle.radial)
1513 filled = RGRADIENT;
1514 else
1515 filled = GRADIENT;
1516 gvrender_box(job, job->clip, filled);
1517 free (clrs[0]);
1518 free(clrs[1]);
1519 }
1520 else {
1522 gvrender_set_pencolor(job, "transparent");
1523 gvrender_box(job, job->clip, FILL); /* filled */
1524 }
1525 }
1526
1527 if ((xd = GD_drawing(g)->xdots))
1528 emit_xdot (job, xd);
1529}
1530
1531static void setup_page(GVJ_t * job)
1532{
1533 point pagesArrayElem = job->pagesArrayElem, pagesArraySize = job->pagesArraySize;
1534
1535 if (job->rotation) {
1536 pagesArrayElem = exch_xy(pagesArrayElem);
1537 pagesArraySize = exch_xy(pagesArraySize);
1538 }
1539
1540 /* establish current box in graph units */
1541 job->pageBox.LL.x = pagesArrayElem.x * job->pageSize.x - job->pad.x;
1542 job->pageBox.LL.y = pagesArrayElem.y * job->pageSize.y - job->pad.y;
1543 job->pageBox.UR.x = job->pageBox.LL.x + job->pageSize.x;
1544 job->pageBox.UR.y = job->pageBox.LL.y + job->pageSize.y;
1545
1546 /* maximum boundingBox in device units and page orientation */
1547 if (job->common->viewNum == 0)
1548 job->boundingBox = job->pageBoundingBox;
1549 else
1551
1552 if (job->flags & GVDEVICE_EVENTS) {
1553 job->clip.LL.x = job->focus.x - job->view.x / 2.;
1554 job->clip.LL.y = job->focus.y - job->view.y / 2.;
1555 job->clip.UR.x = job->focus.x + job->view.x / 2.;
1556 job->clip.UR.y = job->focus.y + job->view.y / 2.;
1557 }
1558 else {
1559 job->clip.LL.x = job->focus.x + job->pageSize.x * (pagesArrayElem.x - pagesArraySize.x / 2.);
1560 job->clip.LL.y = job->focus.y + job->pageSize.y * (pagesArrayElem.y - pagesArraySize.y / 2.);
1561 job->clip.UR.x = job->clip.LL.x + job->pageSize.x;
1562 job->clip.UR.y = job->clip.LL.y + job->pageSize.y;
1563 }
1564
1565 /* CAUTION - job->translation was difficult to get right. */
1566 // Test with and without asymmetric margins, e.g: -Gmargin="1,0"
1567 if (job->rotation) {
1568 job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
1569 if ((job->flags & GVRENDER_Y_GOES_DOWN) || Y_invert)
1570 job->translation.x = - job->clip.UR.x - job->canvasBox.LL.x / job->zoom;
1571 else
1572 job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
1573 }
1574 else {
1575 /* pre unscale margins to keep them constant under scaling */
1576 job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
1577 if ((job->flags & GVRENDER_Y_GOES_DOWN) || Y_invert)
1578 job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
1579 else
1580 job->translation.y = - job->clip.LL.y + job->canvasBox.LL.y / job->zoom;
1581 }
1582}
1583
1584static bool node_in_layer(GVJ_t *job, graph_t * g, node_t * n)
1585{
1586 if (job->numLayers <= 1)
1587 return true;
1588 const char *const pn = late_string(n, N_layer, "");
1589 if (selectedlayer(job, pn))
1590 return true;
1591 if (!streq(pn, ""))
1592 return false;
1593 if (agfstedge(g, n) == NULL)
1594 return true;
1595 for (edge_t *e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
1596 const char *const pe = late_string(e, E_layer, "");
1597 if (streq(pe, "") || selectedlayer(job, pe))
1598 return true;
1599 }
1600 return false;
1601}
1602
1603static bool edge_in_layer(GVJ_t *job, edge_t * e)
1604{
1605 if (job->numLayers <= 1)
1606 return true;
1607 const char *const pe = late_string(e, E_layer, "");
1608 if (selectedlayer(job, pe))
1609 return true;
1610 if (!streq(pe, ""))
1611 return false;
1612 for (int cnt = 0; cnt < 2; cnt++) {
1613 const char *const pn = late_string(cnt < 1 ? agtail(e) : aghead(e), N_layer, "");
1614 if (streq(pn, "") || selectedlayer(job, pn))
1615 return true;
1616 }
1617 return false;
1618}
1619
1620static bool clust_in_layer(GVJ_t *job, graph_t * sg)
1621{
1622 if (job->numLayers <= 1)
1623 return true;
1624 const char *const pg = late_string(sg, agattr_text(sg, AGRAPH, "layer", 0), "");
1625 if (selectedlayer(job, pg))
1626 return true;
1627 if (!streq(pg, ""))
1628 return false;
1629 for (node_t *n = agfstnode(sg); n; n = agnxtnode(sg, n))
1630 if (node_in_layer(job, sg, n))
1631 return true;
1632 return false;
1633}
1634
1635static bool node_in_box(node_t *n, boxf b)
1636{
1637 return boxf_overlap(ND_bb(n), b);
1638}
1639
1641
1642static void emit_begin_node(GVJ_t * job, node_t * n)
1643{
1644 obj_state_t *obj;
1645 int flags = job->flags;
1646 int shape;
1647 size_t nump = 0;
1648 polygon_t *poly = NULL;
1649 pointf *vertices, *p = NULL;
1650 pointf coord;
1651 char *s;
1652
1653 obj = push_obj_state(job);
1654 obj->type = NODE_OBJTYPE;
1655 obj->u.n = n;
1656 obj->emit_state = EMIT_NDRAW;
1657
1658 if (flags & GVRENDER_DOES_Z) {
1659 if (GD_odim(agraphof(n)) >=3)
1660 obj->z = POINTS(ND_pos(n)[2]);
1661 else
1662 obj->z = 0.0;
1663 }
1664 initObjMapData (job, ND_label(n), n);
1666 && (obj->url || obj->explicit_tooltip)) {
1667
1668 /* checking shape of node */
1669 shape = shapeOf(n);
1670 /* node coordinate */
1671 coord = ND_coord(n);
1672 /* checking if filled style has been set for node */
1673 bool filled = isFilled(n);
1674
1675 bool is_rect = false;
1676 if (shape == SH_POLY || shape == SH_POINT) {
1677 poly = ND_shape_info(n);
1678
1679 /* checking if polygon is regular rectangle */
1680 if (isRect(poly) && (poly->peripheries || filled))
1681 is_rect = true;
1682 }
1683
1684 /* When node has polygon shape and requested output supports polygons
1685 * we use a polygon to map the clickable region that is a:
1686 * circle, ellipse, polygon with n side, or point.
1687 * For regular rectangular shape we have use node's bounding box to map clickable region
1688 */
1689 if (poly && !is_rect && (flags & GVRENDER_DOES_MAP_POLYGON)) {
1690
1691 const size_t sides = poly->sides < 3 ? 1 : poly->sides;
1692 const size_t peripheries = poly->peripheries < 2 ? 1 : poly->peripheries;
1693
1694 vertices = poly->vertices;
1695
1696 int nump_int = 0;
1697 if ((s = agget(n, "samplepoints")))
1698 nump_int = atoi(s);
1699 /* We want at least 4 points. For server-side maps, at most 100
1700 * points are allowed. To simplify things to fit with the 120 points
1701 * used for skewed ellipses, we set the bound at 60.
1702 */
1703 nump = (nump_int < 4 || nump_int > 60) ? DFLT_SAMPLE : (size_t)nump_int;
1704 /* use bounding box of text label or node image for mapping
1705 * when polygon has no peripheries and node is not filled
1706 */
1707 if (poly->peripheries == 0 && !filled) {
1709 nump = 2;
1710 p = gv_calloc(nump, sizeof(pointf));
1711 P2RECT(coord, p, ND_lw(n), ND_ht(n) / 2.0 );
1712 }
1713 /* circle or ellipse */
1714 else if (poly->sides < 3 && is_exactly_zero(poly->skew) &&
1715 is_exactly_zero(poly->distortion)) {
1716 if (poly->regular) {
1718 nump = 2; /* center of circle and top right corner of bb */
1719 p = gv_calloc(nump, sizeof(pointf));
1720 p[0].x = coord.x;
1721 p[0].y = coord.y;
1722 /* even vertices contain LL corner of bb */
1723 /* odd vertices contain UR corner of bb */
1724 p[1].x = coord.x + vertices[2*peripheries - 1].x;
1725 p[1].y = coord.y + vertices[2*peripheries - 1].y;
1726 }
1727 else { /* ellipse is treated as polygon */
1729 p = pEllipse(vertices[2 * peripheries - 1].x,
1730 vertices[2 * peripheries - 1].y, nump);
1731 for (size_t i = 0; i < nump; i++) {
1732 p[i].x += coord.x;
1733 p[i].y += coord.y;
1734 }
1735 }
1736 }
1737 /* all other polygonal shape */
1738 else {
1739 assert(peripheries >= 1);
1740 size_t offset = (peripheries - 1) * poly->sides;
1742 /* distorted or skewed ellipses and circles are polygons with 120
1743 * sides. For mapping we convert them into polygon with sample sides
1744 */
1745 if (poly->sides >= nump) {
1746 size_t delta = poly->sides / nump;
1747 p = gv_calloc(nump, sizeof(pointf));
1748 for (size_t i = 0, j = 0; j < nump; i += delta, j++) {
1749 p[j].x = coord.x + vertices[i + offset].x;
1750 p[j].y = coord.y + vertices[i + offset].y;
1751 }
1752 } else {
1753 nump = sides;
1754 p = gv_calloc(nump, sizeof(pointf));
1755 for (size_t i = 0; i < nump; i++) {
1756 p[i].x = coord.x + vertices[i + offset].x;
1757 p[i].y = coord.y + vertices[i + offset].y;
1758 }
1759 }
1760 }
1761 }
1762 else {
1763 /* we have to use the node's bounding box to map clickable region
1764 * when requested output format is not capable of polygons.
1765 */
1767 nump = 2;
1768 p = gv_calloc(nump, sizeof(pointf));
1769 p[0].x = coord.x - ND_lw(n);
1770 p[0].y = coord.y - (ND_ht(n) / 2);
1771 p[1].x = coord.x + ND_rw(n);
1772 p[1].y = coord.y + (ND_ht(n) / 2);
1773 }
1775 gvrender_ptf_A(job, p, p, nump);
1776 obj->url_map_p = p;
1777 obj->url_map_n = nump;
1778 }
1779
1780 saved_color_scheme = setColorScheme(agget(n, "colorscheme"));
1782}
1783
1784static void emit_end_node(GVJ_t * job)
1785{
1786 gvrender_end_node(job);
1787
1788 char *color_scheme = setColorScheme(saved_color_scheme);
1789 free(color_scheme);
1792
1793 pop_obj_state(job);
1794}
1795
1796static void emit_node(GVJ_t * job, node_t * n)
1797{
1798 GVC_t *gvc = job->gvc;
1799 char *s;
1800 char *style;
1801 char **styles = NULL;
1802 char **sp;
1803 char *p;
1804
1805 if (ND_shape(n) /* node has a shape */
1806 && node_in_layer(job, agraphof(n), n) /* and is in layer */
1807 && node_in_box(n, job->clip) /* and is in page/view */
1808 && ND_state(n) != gvc->common.viewNum) /* and not already drawn */
1809 {
1810 ND_state(n) = gvc->common.viewNum; /* mark node as drawn */
1811
1812 gvrender_comment(job, agnameof(n));
1813 s = late_string(n, N_comment, "");
1814 if (s[0])
1815 gvrender_comment(job, s);
1816
1817 style = late_string(n, N_style, "");
1818 if (style[0]) {
1819 styles = parse_style(style);
1820 sp = styles;
1821 while ((p = *sp++)) {
1822 if (streq(p, "invis")) return;
1823 }
1824 }
1825
1826 emit_begin_node(job, n);
1827 ND_shape(n)->fns->codefn(job, n);
1828 if (ND_xlabel(n) && ND_xlabel(n)->set)
1830 emit_end_node(job);
1831 }
1832}
1833
1834/* calculate an offset vector, length d, perpendicular to line p,q */
1835static pointf computeoffset_p(pointf p, pointf q, double d)
1836{
1837 pointf res;
1838 double x = p.x - q.x, y = p.y - q.y;
1839
1840 /* keep d finite as line length approaches 0 */
1841 d /= sqrt(x * x + y * y + EPSILON);
1842 res.x = y * d;
1843 res.y = -x * d;
1844 return res;
1845}
1846
1847/* calculate offset vector, length d, perpendicular to spline p,q,r,s at q&r */
1849 double d)
1850{
1851 pointf res;
1852 double len;
1853 double x = q.x - r.x, y = q.y - r.y;
1854
1855 len = hypot(x, y);
1856 if (len < EPSILON) {
1857 /* control points are on top of each other
1858 use slope between endpoints instead */
1859 x = p.x - s.x, y = p.y - s.y;
1860 /* keep d finite as line length approaches 0 */
1861 len = sqrt(x * x + y * y + EPSILON);
1862 }
1863 d /= len;
1864 res.x = y * d;
1865 res.y = -x * d;
1866 return res;
1867}
1868
1869static void emit_attachment(GVJ_t * job, textlabel_t * lp, splines * spl)
1870{
1871 pointf sz, AF[3];
1872 const char *s;
1873
1874 for (s = lp->text; *s; s++) {
1875 if (!gv_isspace(*s))
1876 break;
1877 }
1878 if (*s == '\0')
1879 return;
1880
1881 sz = lp->dimen;
1882 AF[0] = (pointf){lp->pos.x + sz.x / 2., lp->pos.y - sz.y / 2.};
1883 AF[1] = (pointf){AF[0].x - sz.x, AF[0].y};
1884 AF[2] = dotneato_closest(spl, lp->pos);
1885 /* Don't use edge style to draw attachment */
1887 /* Use font color to draw attachment
1888 - need something unambiguous in case of multicolored parallel edges
1889 - defaults to black for html-like labels
1890 */
1892 gvrender_polyline(job, AF, 3);
1893}
1894
1895/* edges’ colors can be multiple colors separated by ":"
1896 * so we compute a default pencolor with the same number of colors. */
1897static char *default_pencolor(agxbuf *buf, const char *pencolor,
1898 const char *deflt) {
1899 agxbput(buf, deflt);
1900 for (const char *p = pencolor; *p; p++) {
1901 if (*p == ':')
1902 agxbprint(buf, ":%s", deflt);
1903 }
1904 return agxbuse(buf);
1905}
1906
1907static double approxLen (pointf* pts)
1908{
1909 double d = DIST(pts[0],pts[1]);
1910 d += DIST(pts[1],pts[2]);
1911 d += DIST(pts[2],pts[3]);
1912 return d;
1913}
1914
1915/* Given B-spline bz and 0 < t < 1, split bz so that left corresponds to
1916 * the fraction t of the arc length. The new parts are store in left and right.
1917 * The caller needs to free the allocated points.
1918 *
1919 * In the current implementation, we find the Bézier that should contain t by
1920 * treating the control points as a polyline.
1921 * We then split that Bézier.
1922 */
1923static void splitBSpline(bezier *bz, double t, bezier *left, bezier *right) {
1924 const size_t cnt = (bz->size - 1) / 3;
1925 double last, len, sum;
1926 pointf* pts;
1927
1928 if (cnt == 1) {
1929 left->size = 4;
1930 left->list = gv_calloc(4, sizeof(pointf));
1931 right->size = 4;
1932 right->list = gv_calloc(4, sizeof(pointf));
1933 Bezier (bz->list, t, left->list, right->list);
1934 return;
1935 }
1936
1937 double* lens = gv_calloc(cnt, sizeof(double));
1938 sum = 0;
1939 pts = bz->list;
1940 for (size_t i = 0; i < cnt; i++) {
1941 lens[i] = approxLen (pts);
1942 sum += lens[i];
1943 pts += 3;
1944 }
1945 len = t*sum;
1946 sum = 0;
1947 size_t i;
1948 for (i = 0; i < cnt; i++) {
1949 sum += lens[i];
1950 if (sum >= len)
1951 break;
1952 }
1953
1954 left->size = 3*(i+1) + 1;
1955 left->list = gv_calloc(left->size, sizeof(pointf));
1956 right->size = 3*(cnt-i) + 1;
1957 right->list = gv_calloc(right->size, sizeof(pointf));
1958 size_t j;
1959 for (j = 0; j < left->size; j++)
1960 left->list[j] = bz->list[j];
1961 size_t k = j - 4;
1962 for (j = 0; j < right->size; j++)
1963 right->list[j] = bz->list[k++];
1964
1965 last = lens[i];
1966 const double r = (len - (sum - last)) / last;
1967 Bezier (bz->list + 3*i, r, left->list + 3*i, right->list);
1968
1969 free (lens);
1970}
1971
1972/* Draw an edge as a sequence of colors.
1973 * Not sure how to handle multiple B-splines, so do a naive
1974 * implementation.
1975 * Return non-zero if color spec is incorrect
1976 */
1977static int multicolor(GVJ_t *job, edge_t *e, char **styles, const char *colors,
1978 double arrowsize, double penwidth) {
1979 bezier bz;
1980 bezier bz0, bz_l, bz_r;
1981 int rv;
1982 colorsegs_t segs;
1983 char* endcolor = NULL;
1984 double left;
1985 int first; /* first segment with t > 0 */
1986
1987 rv = parseSegs(colors, &segs);
1988 if (rv > 1) {
1989 Agraph_t* g = agraphof(agtail(e));
1990 agerr (AGPREV, "in edge %s%s%s\n", agnameof(agtail(e)), (agisdirected(g)?" -> ":" -- "), agnameof(aghead(e)));
1991
1992 if (rv == 2)
1993 return 1;
1994 }
1995 else if (rv == 1)
1996 return 1;
1997
1998
1999 for (size_t i = 0; i < ED_spl(e)->size; i++) {
2000 left = 1;
2001 bz = ED_spl(e)->list[i];
2002 first = 1;
2003 for (size_t j = 0; j < LIST_SIZE(&segs); ++j) {
2004 const colorseg_t s = LIST_GET(&segs, j);
2005 if (s.color == NULL) break;
2006 if (AEQ0(s.t)) continue;
2007 gvrender_set_pencolor(job, s.color);
2008 left -= s.t;
2009 endcolor = s.color;
2010 if (first) {
2011 first = 0;
2012 splitBSpline(&bz, s.t, &bz_l, &bz_r);
2013 gvrender_beziercurve(job, bz_l.list, bz_l.size, 0);
2014 free (bz_l.list);
2015 if (AEQ0(left)) {
2016 free (bz_r.list);
2017 break;
2018 }
2019 }
2020 else if (AEQ0(left)) {
2021 gvrender_beziercurve(job, bz_r.list, bz_r.size, 0);
2022 free (bz_r.list);
2023 break;
2024 }
2025 else {
2026 bz0 = bz_r;
2027 splitBSpline(&bz0, s.t / (left + s.t), &bz_l, &bz_r);
2028 free (bz0.list);
2029 gvrender_beziercurve(job, bz_l.list, bz_l.size, 0);
2030 free (bz_l.list);
2031 }
2032
2033 }
2034 /* arrow_gen resets the job style (How? FIXME)
2035 * If we have more splines to do, restore the old one.
2036 * Use local copy of penwidth to work around reset.
2037 */
2038 if (bz.sflag) {
2041 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2042 }
2043 if (bz.eflag) {
2044 gvrender_set_pencolor(job, endcolor);
2045 gvrender_set_fillcolor(job, endcolor);
2046 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2047 }
2048 if (ED_spl(e)->size > 1 && (bz.sflag || bz.eflag) && styles)
2049 gvrender_set_style(job, styles);
2050 }
2051 LIST_FREE(&segs);
2052 return 0;
2053}
2054
2055static void free_stroke(stroke_t sp) {
2056 free(sp.vertices);
2057}
2058
2059typedef double (*radfunc_t)(double,double,double);
2060
2061static double forfunc (double curlen, double totallen, double initwid)
2062{
2063 return (1 - curlen / totallen) * initwid / 2.0;
2064}
2065
2066static double revfunc (double curlen, double totallen, double initwid)
2067{
2068 return curlen / totallen * initwid / 2.0;
2069}
2070
2071static double nonefunc (double curlen, double totallen, double initwid)
2072{
2073 (void)curlen;
2074 (void)totallen;
2075
2076 return initwid / 2.0;
2077}
2078
2079static double bothfunc (double curlen, double totallen, double initwid)
2080{
2081 double fr = curlen/totallen;
2082 if (fr <= 0.5) return fr * initwid;
2083 return (1 - fr) * initwid;
2084}
2085
2086static radfunc_t
2088{
2089 char* attr;
2090 if (E_dir && ((attr = agxget(e, E_dir)))[0]) {
2091 if (streq(attr, "forward")) return forfunc;
2092 if (streq(attr, "back")) return revfunc;
2093 if (streq(attr, "both")) return bothfunc;
2094 if (streq(attr, "none")) return nonefunc;
2095 }
2097}
2098
2099/* find_prev_distinct:
2100 * Find the previous point that is not a duplicate of pts[i].
2101 * Returns `SIZE_MAX` if no such point exists.
2102 */
2103static size_t find_prev_distinct(const pointf *pts, size_t i) {
2104 const double TOLERANCE = 0.01;
2105 for (size_t j = i - 1; j != SIZE_MAX; j--) {
2106 double dx = pts[j].x - pts[i].x;
2107 double dy = pts[j].y - pts[i].y;
2108 if (hypot(dx, dy) > TOLERANCE) {
2109 return j;
2110 }
2111 }
2112 return SIZE_MAX;
2113}
2114
2115/* find_next_distinct:
2116 * Find the next point that is not a duplicate of pts[i].
2117 * Returns `SIZE_MAX` if no such point exists.
2118 */
2119static size_t find_next_distinct(const pointf *pts, size_t i, size_t n) {
2120 const double TOLERANCE = 0.01;
2121 for (size_t j = i + 1; j < n; j++) {
2122 double dx = pts[j].x - pts[i].x;
2123 double dy = pts[j].y - pts[i].y;
2124 if (hypot(dx, dy) > TOLERANCE) {
2125 return j;
2126 }
2127 }
2128 return SIZE_MAX;
2129}
2130
2131
2132/* Structure to hold corner information for truncation */
2133typedef struct {
2134 size_t idx; /* Index of corner point */
2135 pointf trunc_prev; /* Truncation point toward previous segment */
2136 pointf trunc_next; /* Truncation point toward next segment */
2137 pointf wedge_center; /* Center of arc wedge */
2138 double angle1, angle2; /* Arc angles */
2140
2141/* Comparison function for sorting corners by index */
2142static int compare_corners(const void *a, const void *b) {
2143 const corner_info_t *ca = a;
2144 const corner_info_t *cb = b;
2145 if (ca->idx < cb->idx) return -1;
2146 if (ca->idx > cb->idx) return 1;
2147 return 0;
2148}
2149
2150/* calculate_wedge_parameters:
2151 * Calculate the wedge center and angles for an orthogonal corner based on
2152 * the normalized direction vectors.
2153 */
2155 double dx1, double dy1, double dx2, double dy2,
2156 double radius, bool seg1_horiz, bool seg2_vert)
2157{
2158 if (seg1_horiz && seg2_vert) {
2159 if (dx1 > 0 && dy2 < 0) {
2160 /* Right then down */
2161 ci->wedge_center.x = curr.x - radius;
2162 ci->wedge_center.y = curr.y - radius;
2163 ci->angle1 = 0;
2164 ci->angle2 = M_PI / 2;
2165 } else if (dx1 > 0 && dy2 > 0) {
2166 /* Right then up */
2167 ci->wedge_center.x = curr.x - radius;
2168 ci->wedge_center.y = curr.y + radius;
2169 ci->angle1 = -M_PI / 2;
2170 ci->angle2 = 0;
2171 } else if (dx1 < 0 && dy2 < 0) {
2172 /* Left then down */
2173 ci->wedge_center.x = curr.x + radius;
2174 ci->wedge_center.y = curr.y - radius;
2175 ci->angle1 = M_PI / 2;
2176 ci->angle2 = M_PI;
2177 } else {
2178 /* Left then up */
2179 ci->wedge_center.x = curr.x + radius;
2180 ci->wedge_center.y = curr.y + radius;
2181 ci->angle1 = M_PI;
2182 ci->angle2 = 3 * M_PI / 2;
2183 }
2184 } else {
2185 if (dy1 < 0 && dx2 > 0) {
2186 /* Down then right */
2187 ci->wedge_center.x = curr.x + radius;
2188 ci->wedge_center.y = curr.y + radius;
2189 ci->angle1 = M_PI;
2190 ci->angle2 = 3 * M_PI / 2;
2191 } else if (dy1 < 0 && dx2 < 0) {
2192 /* Down then left */
2193 ci->wedge_center.x = curr.x - radius;
2194 ci->wedge_center.y = curr.y + radius;
2195 ci->angle1 = 3 * M_PI / 2;
2196 ci->angle2 = 2 * M_PI;
2197 } else if (dy1 > 0 && dx2 > 0) {
2198 /* Up then right */
2199 ci->wedge_center.x = curr.x + radius;
2200 ci->wedge_center.y = curr.y - radius;
2201 ci->angle1 = M_PI / 2;
2202 ci->angle2 = M_PI;
2203 } else {
2204 /* Up then left */
2205 ci->wedge_center.x = curr.x - radius;
2206 ci->wedge_center.y = curr.y - radius;
2207 ci->angle1 = 0;
2208 ci->angle2 = M_PI / 2;
2209 }
2210 }
2211}
2212
2213typedef LIST(corner_info_t) corners_t;
2214
2215/* process_corner:
2216 * Process a detected orthogonal corner and add it to the corners array.
2217 * Returns true if the corner was added, false if it was a duplicate.
2218 */
2219static bool process_corner(corners_t *corners, const pointf *pts, size_t i,
2220 pointf curr, double dx1, double dy1, double dx2,
2221 double dy2, double radius, bool seg1_horiz,
2222 bool seg2_vert) {
2223 /* Check if we already detected this corner (skip duplicates) */
2224 const double DUP_TOL = 0.01;
2225 for (size_t j = 0; j < LIST_SIZE(corners); j++) {
2226 const corner_info_t ci = LIST_GET(corners, j);
2227 pointf existing_corner = pts[ci.idx];
2228 if (hypot(curr.x - existing_corner.x, curr.y - existing_corner.y) < DUP_TOL) {
2229 return false;
2230 }
2231 }
2232
2233 LIST_APPEND(corners, (corner_info_t){0});
2234 corner_info_t *const ci = LIST_BACK(corners);
2235 ci->idx = i;
2236
2237 /* Normalize direction vectors */
2238 double len1 = hypot(dx1, dy1);
2239 double len2 = hypot(dx2, dy2);
2240 dx1 /= len1;
2241 dy1 /= len1;
2242 dx2 /= len2;
2243 dy2 /= len2;
2244
2245 /* Calculate truncation points (move radius distance away from corner) */
2246 ci->trunc_prev.x = curr.x - dx1 * radius;
2247 ci->trunc_prev.y = curr.y - dy1 * radius;
2248 ci->trunc_next.x = curr.x + dx2 * radius;
2249 ci->trunc_next.y = curr.y + dy2 * radius;
2250
2251 /* Calculate wedge center and angles */
2252 calculate_wedge_parameters(ci, curr, dx1, dy1, dx2, dy2, radius, seg1_horiz, seg2_vert);
2253
2254 return true;
2255}
2256
2257/* find_ortho_corners:
2258 * Identify all orthogonal corners and compute truncation/arc information.
2259 * Fills corners array.
2260 * Caller must pre-allocate corners array with at least n elements.
2261 */
2262static void find_ortho_corners(const pointf *pts, size_t n, double radius,
2263 corners_t *corners) {
2264 const double TOL = 0.1;
2265
2266 for (size_t i = 0; i < n; i++) {
2267 const size_t prev_idx = find_prev_distinct(pts, i);
2268 const size_t next_idx = find_next_distinct(pts, i, n);
2269
2270 if (prev_idx == SIZE_MAX || next_idx == SIZE_MAX) continue;
2271
2272 pointf prev = pts[prev_idx];
2273 pointf curr = pts[i];
2274 pointf next = pts[next_idx];
2275
2276 /* Get direction vectors */
2277 double dx1 = curr.x - prev.x;
2278 double dy1 = curr.y - prev.y;
2279 double dx2 = next.x - curr.x;
2280 double dy2 = next.y - curr.y;
2281
2282 /* Check if this is an orthogonal corner */
2283 bool seg1_horiz = fabs(dy1) < TOL && fabs(dx1) > TOL;
2284 bool seg1_vert = fabs(dx1) < TOL && fabs(dy1) > TOL;
2285 bool seg2_horiz = fabs(dy2) < TOL && fabs(dx2) > TOL;
2286 bool seg2_vert = fabs(dx2) < TOL && fabs(dy2) > TOL;
2287
2288 bool is_corner = (seg1_horiz && seg2_vert) || (seg1_vert && seg2_horiz);
2289
2290 if (is_corner) {
2291 process_corner(corners, pts, i, curr, dx1, dy1, dx2, dy2, radius,
2292 seg1_horiz, seg2_vert);
2293 }
2294 }
2295}
2296
2297/* render_corner_arc:
2298 * Extract and render the arc portion from a wedge polyline.
2299 * The wedge includes center point and return paths which we skip.
2300 */
2301static void render_corner_arc(GVJ_t *job, const Ppolyline_t *wedge,
2302 size_t arc_start_idx, size_t arc_point_count,
2303 char *edge_color)
2304{
2305 LIST(pointf) arc_points = {0};
2306 LIST_RESERVE(&arc_points, arc_point_count);
2307 for (size_t j = 0; j < arc_point_count; j++) {
2308 LIST_APPEND(&arc_points, wedge->ps[arc_start_idx + j]);
2309 }
2310
2311 /* Draw only the arc curve (no straight edges back to center) */
2312 if (edge_color && edge_color[0]) {
2313 gvrender_set_pencolor(job, edge_color);
2314 } else {
2316 }
2317 gvrender_polyline(job, LIST_FRONT(&arc_points), arc_point_count);
2318
2319 LIST_FREE(&arc_points);
2320}
2321
2322/* draw_ortho_corner_markers:
2323 * Draw 90-degree arc curves at orthogonal edge corners.
2324 * Draws only the arc portion (not the straight wedge edges).
2325 */
2326static void draw_ortho_corner_markers(GVJ_t *job, const corners_t *corners,
2327 double radius, char *edge_color) {
2328 for (size_t i = 0; i < LIST_SIZE(corners); i++) {
2329 const corner_info_t ci = LIST_GET(corners, i);
2330
2331 /* Generate wedge path */
2332 Ppolyline_t *wedge = ellipticWedge(ci.wedge_center, radius, radius,
2333 ci.angle1, ci.angle2);
2334
2335 if (wedge && wedge->pn > 4) {
2336 /* Extract only the arc portion (skip center at start and close path at end) */
2337 /* Skip duplicates and straight edges: wedge path includes center and return paths */
2338 size_t arc_start_idx = 3; /* Skip center, first arc point, AND duplicate */
2339 size_t arc_end_idx = wedge->pn - 4; /* Skip duplicate endpoint, last arc point, AND center */
2340 size_t arc_point_count = arc_end_idx >= arc_start_idx ? arc_end_idx - arc_start_idx + 1 : 0;
2341
2342 if (arc_point_count >= 2) {
2343 render_corner_arc(job, wedge, arc_start_idx, arc_point_count, edge_color);
2344 }
2345
2346 free(wedge->ps);
2347 free(wedge);
2348 }
2349 }
2350}
2351
2352static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
2353{
2354 int cnum, numsemi = 0;
2355 char *color, *pencolor, *fillcolor;
2356 char *headcolor, *tailcolor, *lastcolor;
2357 char *colors = NULL;
2358 bezier bz;
2359 splines offspl, tmpspl;
2360 pointf pf0, pf1, pf2 = { 0, 0 }, pf3, *offlist, *tmplist;
2361 double arrowsize, numc2, penwidth=job->obj->penwidth;
2362 char* p;
2363 bool tapered = false;
2364 agxbuf buf = {0};
2365
2366#define SEP 2.0
2367
2368 char *previous_color_scheme = setColorScheme(agget(e, "colorscheme"));
2369 if (ED_spl(e)) {
2370 arrowsize = late_double(e, E_arrowsz, 1.0, 0.0);
2371 color = late_string(e, E_color, "");
2372
2373 if (styles) {
2374 char** sp = styles;
2375 while ((p = *sp++)) {
2376 if (streq(p, "tapered")) {
2377 tapered = true;
2378 break;
2379 }
2380 }
2381 }
2382
2383 /* need to know how many colors separated by ':' */
2384 size_t numc = 0;
2385 for (p = color; *p; p++) {
2386 if (*p == ':')
2387 numc++;
2388 else if (*p == ';')
2389 numsemi++;
2390 }
2391
2392 if (numsemi && numc) {
2393 if (multicolor(job, e, styles, color, arrowsize, penwidth)) {
2395 }
2396 else
2397 goto done;
2398 }
2399
2400 fillcolor = pencolor = color;
2401 if (ED_gui_state(e) & GUI_STATE_ACTIVE) {
2402 pencolor = default_pencolor(&buf, pencolor, DEFAULT_ACTIVEPENCOLOR);
2403 fillcolor = DEFAULT_ACTIVEFILLCOLOR;
2404 }
2405 else if (ED_gui_state(e) & GUI_STATE_SELECTED) {
2406 pencolor = default_pencolor(&buf, pencolor, DEFAULT_SELECTEDPENCOLOR);
2407 fillcolor = DEFAULT_SELECTEDFILLCOLOR;
2408 }
2409 else if (ED_gui_state(e) & GUI_STATE_DELETED) {
2410 pencolor = default_pencolor(&buf, pencolor, DEFAULT_DELETEDPENCOLOR);
2411 fillcolor = DEFAULT_DELETEDFILLCOLOR;
2412 }
2413 else if (ED_gui_state(e) & GUI_STATE_VISITED) {
2414 pencolor = default_pencolor(&buf, pencolor, DEFAULT_VISITEDPENCOLOR);
2415 fillcolor = DEFAULT_VISITEDFILLCOLOR;
2416 }
2417 else
2418 fillcolor = late_nnstring(e, E_fillcolor, color);
2419 if (pencolor != color)
2420 gvrender_set_pencolor(job, pencolor);
2421 if (fillcolor != color)
2422 gvrender_set_fillcolor(job, fillcolor);
2423 color = pencolor;
2424
2425 if (tapered) {
2426 if (*color == '\0') color = DEFAULT_COLOR;
2427 if (*fillcolor == '\0') fillcolor = DEFAULT_COLOR;
2428 gvrender_set_pencolor(job, "transparent");
2430 bz = ED_spl(e)->list[0];
2431 stroke_t stp = taper(&bz, taperfun (e), penwidth);
2432 assert(stp.nvertices <= INT_MAX);
2433 gvrender_polygon(job, stp.vertices, stp.nvertices, 1);
2434 free_stroke(stp);
2436 if (fillcolor != color)
2437 gvrender_set_fillcolor(job, fillcolor);
2438 if (bz.sflag) {
2439 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2440 }
2441 if (bz.eflag) {
2442 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2443 }
2444 }
2445 /* if more than one color - then generate parallel Béziers, one per color */
2446 else if (numc) {
2447 /* calculate and save offset vector spline and initialize first offset spline */
2448 tmpspl.size = offspl.size = ED_spl(e)->size;
2449 offspl.list = gv_calloc(offspl.size, sizeof(bezier));
2450 tmpspl.list = gv_calloc(tmpspl.size, sizeof(bezier));
2451 numc2 = (2 + (double)numc) / 2.0;
2452 for (size_t i = 0; i < offspl.size; i++) {
2453 bz = ED_spl(e)->list[i];
2454 tmpspl.list[i].size = offspl.list[i].size = bz.size;
2455 offlist = offspl.list[i].list = gv_calloc(bz.size, sizeof(pointf));
2456 tmplist = tmpspl.list[i].list = gv_calloc(bz.size, sizeof(pointf));
2457 pf3 = bz.list[0];
2458 size_t j;
2459 for (j = 0; j < bz.size - 1; j += 3) {
2460 pf0 = pf3;
2461 pf1 = bz.list[j + 1];
2462 /* calculate perpendicular vectors for each Bézier point */
2463 if (j == 0) /* first segment, no previous pf2 */
2464 offlist[j] = computeoffset_p(pf0, pf1, SEP);
2465 else /* i.e. pf2 is available from previous segment */
2466 offlist[j] = computeoffset_p(pf2, pf1, SEP);
2467 pf2 = bz.list[j + 2];
2468 pf3 = bz.list[j + 3];
2469 offlist[j + 1] = offlist[j + 2] =
2470 computeoffset_qr(pf0, pf1, pf2, pf3, SEP);
2471 /* initialize tmpspl to outermost position */
2472 tmplist[j].x = pf0.x - numc2 * offlist[j].x;
2473 tmplist[j].y = pf0.y - numc2 * offlist[j].y;
2474 tmplist[j + 1].x = pf1.x - numc2 * offlist[j + 1].x;
2475 tmplist[j + 1].y = pf1.y - numc2 * offlist[j + 1].y;
2476 tmplist[j + 2].x = pf2.x - numc2 * offlist[j + 2].x;
2477 tmplist[j + 2].y = pf2.y - numc2 * offlist[j + 2].y;
2478 }
2479 /* last segment, no next pf1 */
2480 offlist[j] = computeoffset_p(pf2, pf3, SEP);
2481 tmplist[j].x = pf3.x - numc2 * offlist[j].x;
2482 tmplist[j].y = pf3.y - numc2 * offlist[j].y;
2483 }
2484 lastcolor = headcolor = tailcolor = color;
2485 colors = gv_strdup(color);
2486 for (cnum = 0, color = strtok(colors, ":"); color;
2487 cnum++, color = strtok(0, ":")) {
2488 if (!color[0])
2490 if (color != lastcolor) {
2494 }
2495 lastcolor = color;
2496 }
2497 if (cnum == 0)
2498 headcolor = tailcolor = color;
2499 if (cnum == 1)
2500 tailcolor = color;
2501 for (size_t i = 0; i < tmpspl.size; i++) {
2502 tmplist = tmpspl.list[i].list;
2503 offlist = offspl.list[i].list;
2504 for (size_t j = 0; j < tmpspl.list[i].size; j++) {
2505 tmplist[j].x += offlist[j].x;
2506 tmplist[j].y += offlist[j].y;
2507 }
2508 gvrender_beziercurve(job, tmplist, tmpspl.list[i].size, 0);
2509 }
2510 }
2511 if (bz.sflag) {
2512 if (color != tailcolor) {
2513 color = tailcolor;
2517 }
2518 }
2519 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2520 arrowsize, penwidth, bz.sflag);
2521 }
2522 if (bz.eflag) {
2523 if (color != headcolor) {
2524 color = headcolor;
2528 }
2529 }
2530 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2531 arrowsize, penwidth, bz.eflag);
2532 }
2533 free(colors);
2534 for (size_t i = 0; i < offspl.size; i++) {
2535 free(offspl.list[i].list);
2536 free(tmpspl.list[i].list);
2537 }
2538 free(offspl.list);
2539 free(tmpspl.list);
2540 } else {
2542 if (color[0]) {
2544 gvrender_set_fillcolor(job, fillcolor);
2545 } else {
2547 if (fillcolor[0])
2548 gvrender_set_fillcolor(job, fillcolor);
2549 else
2551 }
2552 }
2553 for (size_t i = 0; i < ED_spl(e)->size; i++) {
2554 bz = ED_spl(e)->list[i];
2555
2556 /* Check if this edge has orthogonal routing and wants rounded corners */
2557 char *splines_attr = agget(agraphof(aghead(e)), "splines");
2558 bool is_ortho = splines_attr && streq(splines_attr, "ortho");
2559
2560 /* Check for rounded style or explicit radius attribute */
2561 bool want_rounded = false;
2562 double radius = 0.0;
2563
2564 /* First check if style=rounded */
2565 if (styles) {
2566 for (char **sp = styles; *sp; sp++) {
2567 if (streq(*sp, "rounded")) {
2568 want_rounded = true;
2569 break;
2570 }
2571 }
2572 }
2573
2574 /* Then check for explicit radius attribute (overrides style) */
2575 char *radius_attr = agget(e, "radius");
2576 if (radius_attr && radius_attr[0]) {
2577 radius = atof(radius_attr);
2578 want_rounded = radius > 0;
2579 }
2580
2581 /* If no explicit radius, use default when style=rounded */
2582 if (want_rounded && radius == 0.0) {
2583 radius = fmax(12.0, penwidth * 8.0);
2584 }
2585
2586 if (is_ortho && want_rounded && radius > 0) {
2587 /* Truncate edge at corners and draw arcs */
2588 corners_t corners = {0};
2589 LIST_RESERVE(&corners, bz.size);
2590 find_ortho_corners(bz.list, bz.size, radius, &corners);
2591
2592 if (!LIST_IS_EMPTY(&corners)) {
2593 /* Sort corners by index */
2594 LIST_SORT(&corners, compare_corners);
2595
2596 /* Render segments between corners */
2597 const double CORNER_TOL = 0.01;
2598 size_t seg_start_idx = 0;
2599 pointf seg_start_pt = bz.list[0];
2600
2601 for (size_t c = 0; c <= LIST_SIZE(&corners); c++) {
2602 size_t seg_end_idx;
2603 pointf seg_end_pt;
2604
2605 if (c < LIST_SIZE(&corners)) {
2606 /* Segment ends at this corner's trunc_prev */
2607 seg_end_idx = LIST_GET(&corners, c).idx;
2608 seg_end_pt = LIST_GET(&corners, c).trunc_prev;
2609 } else {
2610 /* Last segment ends at final point */
2611 seg_end_idx = bz.size - 1;
2612 seg_end_pt = bz.list[bz.size - 1];
2613 }
2614
2615 /* Build segment */
2616 LIST(pointf) seg_pts = {0};
2617 LIST_APPEND(&seg_pts, seg_start_pt);
2618 for (size_t pt = seg_start_idx + 1; pt < seg_end_idx; pt++) {
2619 bool at_corner = false;
2620 for (size_t cc = 0; cc < LIST_SIZE(&corners); cc++) {
2621 pointf corner_pt = bz.list[LIST_GET(&corners, cc).idx];
2622 if (hypot(bz.list[pt].x - corner_pt.x, bz.list[pt].y - corner_pt.y) < CORNER_TOL) {
2623 at_corner = true;
2624 break;
2625 }
2626 }
2627 if (!at_corner) {
2628 LIST_APPEND(&seg_pts, bz.list[pt]);
2629 }
2630 }
2631 LIST_APPEND(&seg_pts, seg_end_pt);
2632
2633 /* Render this segment as polyline (straight lines, not bezier) */
2634 gvrender_polyline(job, LIST_FRONT(&seg_pts), LIST_SIZE(&seg_pts));
2635 LIST_FREE(&seg_pts);
2636
2637 /* Prepare for next segment */
2638 if (c < LIST_SIZE(&corners)) {
2639 /* Skip all duplicates of this corner */
2640 size_t next_idx = LIST_GET(&corners, c).idx + 1;
2641 while (next_idx < bz.size) {
2642 bool at_corner = false;
2643 for (size_t cc = 0; cc < LIST_SIZE(&corners); cc++) {
2644 pointf corner_pt = bz.list[LIST_GET(&corners, cc).idx];
2645 if (hypot(bz.list[next_idx].x - corner_pt.x,
2646 bz.list[next_idx].y - corner_pt.y) < CORNER_TOL) {
2647 at_corner = true;
2648 break;
2649 }
2650 }
2651 if (!at_corner) break;
2652 next_idx++;
2653 }
2654 seg_start_idx = next_idx;
2655 seg_start_pt = LIST_GET(&corners, c).trunc_next;
2656 }
2657 }
2658
2659 /* Draw corner arcs to fill the gaps */
2660 draw_ortho_corner_markers(job, &corners, radius, color);
2661 } else {
2662 /* No corners found, render normally */
2663 gvrender_beziercurve(job, bz.list, bz.size, 0);
2664 }
2665 LIST_FREE(&corners);
2666 } else {
2667 /* Non-orthogonal edge, render normally */
2668 gvrender_beziercurve(job, bz.list, bz.size, 0);
2669 }
2670
2671 if (bz.sflag) {
2672 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2673 arrowsize, penwidth, bz.sflag);
2674 }
2675 if (bz.eflag) {
2676 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2677 arrowsize, penwidth, bz.eflag);
2678 }
2679 if (ED_spl(e)->size > 1 && (bz.sflag || bz.eflag) && styles)
2680 gvrender_set_style(job, styles);
2681 }
2682 }
2683 }
2684
2685done:;
2686 char *color_scheme = setColorScheme(previous_color_scheme);
2687 free(color_scheme);
2688 free(previous_color_scheme);
2689 agxbfree(&buf);
2690}
2691
2692static bool edge_in_box(edge_t *e, boxf b)
2693{
2694 splines *spl;
2695 textlabel_t *lp;
2696
2697 spl = ED_spl(e);
2698 if (spl && boxf_overlap(spl->bb, b))
2699 return true;
2700
2701 lp = ED_label(e);
2702 if (lp && overlap_label(lp, b))
2703 return true;
2704
2705 lp = ED_xlabel(e);
2706 if (lp && lp->set && overlap_label(lp, b))
2707 return true;
2708
2709 return false;
2710}
2711
2712static void emit_begin_edge(GVJ_t *job, edge_t *e, char **styles) {
2713 obj_state_t *obj;
2714 int flags = job->flags;
2715 char *s;
2716 textlabel_t *lab = NULL, *tlab = NULL, *hlab = NULL;
2717 char *dflt_url = NULL;
2718 char *dflt_target = NULL;
2719 double penwidth;
2720
2721 obj = push_obj_state(job);
2722 obj->type = EDGE_OBJTYPE;
2723 obj->u.e = e;
2724 obj->emit_state = EMIT_EDRAW;
2725 if (ED_label(e) && !ED_label(e)->html && mapbool(agget(e, "labelaligned")))
2726 obj->labeledgealigned = true;
2727
2728 /* We handle the edge style and penwidth here because the width
2729 * is needed below for calculating polygonal image maps
2730 */
2731 if (styles && ED_spl(e))
2732 gvrender_set_style(job, styles);
2733
2734 if (E_penwidth && (s = agxget(e, E_penwidth)) && s[0]) {
2735 penwidth = late_double(e, E_penwidth, 1.0, 0.0);
2737 }
2738
2739 if (flags & GVRENDER_DOES_Z) {
2740 if (GD_odim(agraphof(agtail(e))) >= 3) {
2741 obj->tail_z = POINTS(ND_pos(agtail(e))[2]);
2742 obj->head_z = POINTS(ND_pos(aghead(e))[2]);
2743 } else {
2744 obj->tail_z = obj->head_z = 0.0;
2745 }
2746 }
2747
2749 if ((lab = ED_label(e)))
2750 obj->label = lab->text;
2751 obj->taillabel = obj->headlabel = obj->xlabel = obj->label;
2752 if ((tlab = ED_xlabel(e)))
2753 obj->xlabel = tlab->text;
2754 if ((tlab = ED_tail_label(e)))
2755 obj->taillabel = tlab->text;
2756 if ((hlab = ED_head_label(e)))
2757 obj->headlabel = hlab->text;
2758 }
2759
2760 if (flags & GVRENDER_DOES_MAPS) {
2761 agxbuf xb = {0};
2762
2763 s = getObjId(job, e, &xb);
2764 obj->id = strdup_and_subst_obj(s, e);
2765 agxbfree(&xb);
2766
2767 if (((s = agget(e, "href")) && s[0]) || ((s = agget(e, "URL")) && s[0]))
2768 dflt_url = strdup_and_subst_obj(s, e);
2769 if (((s = agget(e, "edgehref")) && s[0]) ||
2770 ((s = agget(e, "edgeURL")) && s[0]))
2771 obj->url = strdup_and_subst_obj(s, e);
2772 else if (dflt_url)
2773 obj->url = gv_strdup(dflt_url);
2774 if (((s = agget(e, "labelhref")) && s[0]) ||
2775 ((s = agget(e, "labelURL")) && s[0]))
2776 obj->labelurl = strdup_and_subst_obj(s, e);
2777 else if (dflt_url)
2778 obj->labelurl = gv_strdup(dflt_url);
2779 if (((s = agget(e, "tailhref")) && s[0]) ||
2780 ((s = agget(e, "tailURL")) && s[0])) {
2781 obj->tailurl = strdup_and_subst_obj(s, e);
2782 obj->explicit_tailurl = true;
2783 } else if (dflt_url)
2784 obj->tailurl = gv_strdup(dflt_url);
2785 if (((s = agget(e, "headhref")) && s[0]) ||
2786 ((s = agget(e, "headURL")) && s[0])) {
2787 obj->headurl = strdup_and_subst_obj(s, e);
2788 obj->explicit_headurl = true;
2789 } else if (dflt_url)
2790 obj->headurl = gv_strdup(dflt_url);
2791 }
2792
2794 if ((s = agget(e, "target")) && s[0])
2795 dflt_target = strdup_and_subst_obj(s, e);
2796 if ((s = agget(e, "edgetarget")) && s[0]) {
2797 obj->explicit_edgetarget = true;
2798 obj->target = strdup_and_subst_obj(s, e);
2799 } else if (dflt_target)
2800 obj->target = gv_strdup(dflt_target);
2801 if ((s = agget(e, "labeltarget")) && s[0])
2803 else if (dflt_target)
2804 obj->labeltarget = gv_strdup(dflt_target);
2805 if ((s = agget(e, "tailtarget")) && s[0]) {
2807 obj->explicit_tailtarget = true;
2808 } else if (dflt_target)
2809 obj->tailtarget = gv_strdup(dflt_target);
2810 if ((s = agget(e, "headtarget")) && s[0]) {
2811 obj->explicit_headtarget = true;
2813 } else if (dflt_target)
2814 obj->headtarget = gv_strdup(dflt_target);
2815 }
2816
2818 if (((s = agget(e, "tooltip")) && s[0]) ||
2819 ((s = agget(e, "edgetooltip")) && s[0])) {
2820 char *tooltip = preprocessTooltip(s, e);
2821 obj->tooltip = strdup_and_subst_obj(tooltip, e);
2822 free(tooltip);
2823 obj->explicit_tooltip = true;
2824 } else if (obj->label)
2825 obj->tooltip = gv_strdup(obj->label);
2826
2827 if ((s = agget(e, "labeltooltip")) && s[0]) {
2828 char *tooltip = preprocessTooltip(s, e);
2829 obj->labeltooltip = strdup_and_subst_obj(tooltip, e);
2830 free(tooltip);
2831 obj->explicit_labeltooltip = true;
2832 } else if (obj->label)
2833 obj->labeltooltip = gv_strdup(obj->label);
2834
2835 if ((s = agget(e, "tailtooltip")) && s[0]) {
2836 char *tooltip = preprocessTooltip(s, e);
2837 obj->tailtooltip = strdup_and_subst_obj(tooltip, e);
2838 free(tooltip);
2839 obj->explicit_tailtooltip = true;
2840 } else if (obj->taillabel)
2841 obj->tailtooltip = gv_strdup(obj->taillabel);
2842
2843 if ((s = agget(e, "headtooltip")) && s[0]) {
2844 char *tooltip = preprocessTooltip(s, e);
2845 obj->headtooltip = strdup_and_subst_obj(tooltip, e);
2846 free(tooltip);
2847 obj->explicit_headtooltip = true;
2848 } else if (obj->headlabel)
2849 obj->headtooltip = gv_strdup(obj->headlabel);
2850 }
2851
2852 free(dflt_url);
2853 free(dflt_target);
2854
2856 if (ED_spl(e) && (obj->url || obj->tooltip) &&
2858 splines *spl;
2859 double w2 = fmax(job->obj->penwidth / 2.0, 2.0);
2860
2861 spl = ED_spl(e);
2862 const size_t ns = spl->size; /* number of splines */
2863 points_t pbs = {0};
2864 pbs_size_t pbs_n = {0};
2865 for (size_t i = 0; i < ns; i++)
2866 map_output_bspline(&pbs, &pbs_n, spl->list + i, w2);
2867 if (!(flags & GVRENDER_DOES_TRANSFORM)) {
2868 size_t nump = 0;
2869 for (size_t i = 0; i < LIST_SIZE(&pbs_n); ++i) {
2870 nump += LIST_GET(&pbs_n, i);
2871 }
2872 gvrender_ptf_A(job, LIST_FRONT(&pbs), LIST_FRONT(&pbs), nump);
2873 }
2874 obj->url_bsplinemap_p = LIST_FRONT(&pbs);
2876 LIST_DETACH(&pbs, &obj->url_map_p, NULL);
2877 obj->url_map_n = *LIST_FRONT(&pbs_n);
2879 }
2880 }
2881
2883 if (obj->url || obj->explicit_tooltip)
2884 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
2885}
2886
2887static void
2888emit_edge_label(GVJ_t* job, textlabel_t* lbl, emit_state_t lkind, int explicit,
2889 char* url, char* tooltip, char* target, char *id, splines* spl)
2890{
2891 int flags = job->flags;
2892 emit_state_t old_emit_state;
2893 char* newid;
2894 agxbuf xb = {0};
2895 char* type;
2896
2897 if (lbl == NULL || !lbl->set) return;
2898 if (id) { /* non-NULL if needed */
2899 switch (lkind) {
2900 case EMIT_ELABEL :
2901 type = "label";
2902 break;
2903 case EMIT_HLABEL :
2904 type = "headlabel";
2905 break;
2906 case EMIT_TLABEL :
2907 type = "taillabel";
2908 break;
2909 default :
2910 UNREACHABLE();
2911 }
2912 agxbprint(&xb, "%s-%s", id, type);
2913 newid = agxbuse(&xb);
2914 }
2915 else
2916 newid = NULL;
2917 old_emit_state = job->obj->emit_state;
2918 job->obj->emit_state = lkind;
2919 if ((url || explicit) && !(flags & EMIT_CLUSTERS_LAST)) {
2920 map_label(job, lbl);
2921 gvrender_begin_anchor(job, url, tooltip, target, newid);
2922 }
2923 emit_label(job, lkind, lbl);
2924 if (spl) emit_attachment(job, lbl, spl);
2925 if (url || explicit) {
2926 if (flags & EMIT_CLUSTERS_LAST) {
2927 map_label(job, lbl);
2928 gvrender_begin_anchor(job, url, tooltip, target, newid);
2929 }
2931 }
2932 agxbfree(&xb);
2933 job->obj->emit_state = old_emit_state;
2934}
2935
2936/* Common logic for setting hot spots at the beginning and end of
2937 * an edge.
2938 * If we are given a value (url, tooltip, target) explicitly set for
2939 * the head/tail, we use that.
2940 * Otherwise, if we are given a value explicitly set for the edge,
2941 * we use that.
2942 * Otherwise, we use whatever the argument value is.
2943 * We also note whether or not the tooltip was explicitly set.
2944 * If the url is non-NULL or the tooltip was explicit, we set
2945 * a hot spot around point p.
2946 */
2947static void nodeIntersect(GVJ_t *job, pointf p, bool explicit_iurl, char *iurl,
2948 bool explicit_itooltip) {
2949 obj_state_t *obj = job->obj;
2950 char* url;
2951 bool explicit;
2952
2953 if (explicit_iurl) url = iurl;
2954 else url = obj->url;
2955 if (explicit_itooltip) {
2956 explicit = true;
2957 }
2958 else if (obj->explicit_tooltip) {
2959 explicit = true;
2960 }
2961 else {
2962 explicit = false;
2963 }
2964
2965 if (url || explicit) {
2966 map_point(job, p);
2967 }
2968}
2969
2970static void emit_end_edge(GVJ_t * job)
2971{
2972 obj_state_t *obj = job->obj;
2973 edge_t *e = obj->u.e;
2974
2975 if (obj->url || obj->explicit_tooltip) {
2977 if (obj->url_bsplinemap_poly_n) {
2978 for (size_t nump = obj->url_bsplinemap_n[0], i = 1;
2979 i < obj->url_bsplinemap_poly_n; i++) {
2980 /* additional polygon maps around remaining Bézier pieces */
2981 obj->url_map_n = obj->url_bsplinemap_n[i];
2982 obj->url_map_p = &(obj->url_bsplinemap_p[nump]);
2984 obj->url, obj->tooltip, obj->target, obj->id);
2986 nump += obj->url_bsplinemap_n[i];
2987 }
2988 }
2989 }
2990 obj->url_map_n = 0; /* null out copy so that it doesn't get freed twice */
2991 obj->url_map_p = NULL;
2992
2993 if (ED_spl(e)) {
2994 pointf p;
2995 bezier bz;
2996
2997 /* process intersection with tail node */
2998 bz = ED_spl(e)->list[0];
2999 if (bz.sflag) /* Arrow at start of splines */
3000 p = bz.sp;
3001 else /* No arrow at start of splines */
3002 p = bz.list[0];
3003 nodeIntersect(job, p, obj->explicit_tailurl != 0, obj->tailurl,
3004 obj->explicit_tailtooltip != 0);
3005
3006 /* process intersection with head node */
3007 bz = ED_spl(e)->list[ED_spl(e)->size - 1];
3008 if (bz.eflag) /* Arrow at end of splines */
3009 p = bz.ep;
3010 else /* No arrow at end of splines */
3011 p = bz.list[bz.size - 1];
3012 nodeIntersect(job, p, obj->explicit_headurl != 0, obj->headurl,
3013 obj->explicit_headtooltip != 0);
3014 }
3015
3018 obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
3019 ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
3022 obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
3023 ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
3026 obj->headurl, obj->headtooltip, obj->headtarget, obj->id,
3027 0);
3030 obj->tailurl, obj->tailtooltip, obj->tailtarget, obj->id,
3031 0);
3032
3033 gvrender_end_edge(job);
3034 pop_obj_state(job);
3035}
3036
3037static void emit_edge(GVJ_t * job, edge_t * e)
3038{
3039 char *s;
3040 char *style;
3041 char **styles = NULL;
3042 char **sp;
3043 char *p;
3044
3045 if (edge_in_box(e, job->clip) && edge_in_layer(job, e) ) {
3046
3047 agxbuf edge = {0};
3048 agxbput(&edge, agnameof(agtail(e)));
3049 if (agisdirected(agraphof(aghead(e))))
3050 agxbput(&edge, "->");
3051 else
3052 agxbput(&edge, "--");
3053 agxbput(&edge, agnameof(aghead(e)));
3055 agxbfree(&edge);
3056
3057 s = late_string(e, E_comment, "");
3058 if (s[0])
3059 gvrender_comment(job, s);
3060
3061 style = late_string(e, E_style, "");
3062 /* We shortcircuit drawing an invisible edge because the arrowhead
3063 * code resets the style to solid, and most of the code generators
3064 * (except PostScript) won't honor a previous style of invis.
3065 */
3066 if (style[0]) {
3067 styles = parse_style(style);
3068 sp = styles;
3069 while ((p = *sp++)) {
3070 if (streq(p, "invis")) return;
3071 }
3072 }
3073
3074 emit_begin_edge(job, e, styles);
3075 emit_edge_graphics (job, e, styles);
3076 emit_end_edge(job);
3077 }
3078}
3079
3080static const char adjust[] = {'l', 'n', 'r'};
3081
3082static void
3084{
3085 bb->UR.x = fmax(bb->UR.x, p.x);
3086 bb->LL.x = fmin(bb->LL.x, p.x);
3087 bb->UR.y = fmax(bb->UR.y, p.y);
3088 bb->LL.y = fmin(bb->LL.y, p.y);
3089}
3090
3091static boxf ptsBB(xdot_point *inpts, size_t numpts, boxf *bb) {
3092 boxf opbb;
3093
3094 opbb.LL.x = opbb.UR.x = inpts->x;
3095 opbb.LL.y = opbb.UR.y = inpts->y;
3096 for (size_t i = 1; i < numpts; i++) {
3097 inpts++;
3098 if (inpts->x < opbb.LL.x)
3099 opbb.LL.x = inpts->x;
3100 else if (inpts->x > opbb.UR.x)
3101 opbb.UR.x = inpts->x;
3102 if (inpts->y < opbb.LL.y)
3103 opbb.LL.y = inpts->y;
3104 else if (inpts->y > opbb.UR.y)
3105 opbb.UR.y = inpts->y;
3106
3107 }
3108 expandBB (bb, opbb.LL);
3109 expandBB (bb, opbb.UR);
3110 return opbb;
3111}
3112
3113static boxf
3114textBB (double x, double y, textspan_t* span)
3115{
3116 boxf bb;
3117 pointf sz = span->size;
3118
3119 switch (span->just) {
3120 case 'l':
3121 bb.LL.x = x;
3122 bb.UR.x = bb.LL.x + sz.x;
3123 break;
3124 case 'n':
3125 bb.LL.x = x - sz.x / 2.0;
3126 bb.UR.x = x + sz.x / 2.0;
3127 break;
3128 case 'r':
3129 bb.UR.x = x;
3130 bb.LL.x = bb.UR.x - sz.x;
3131 break;
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
3589#define NotFirstPage(j) (((j)->layerNum>1)||((j)->pagesArrayElem.x > 0)||((j)->pagesArrayElem.x > 0))
3590
3591static void emit_page(GVJ_t * job, graph_t * g)
3592{
3593 obj_state_t *obj = job->obj;
3594 int flags = job->flags;
3595 size_t nump = 0;
3596 textlabel_t *lab;
3597 pointf *p = NULL;
3598 char* saveid;
3599 agxbuf xb = {0};
3600
3601 /* For the first page, we can use the values generated in emit_begin_graph.
3602 * For multiple pages, we need to generate a new id.
3603 */
3604 bool obj_id_needs_restore = false;
3605 if (NotFirstPage(job)) {
3606 saveid = obj->id;
3607 layerPagePrefix (job, &xb);
3608 agxbput(&xb, saveid == NULL ? "layer" : saveid);
3609 obj->id = agxbuse(&xb);
3610 obj_id_needs_restore = true;
3611 }
3612 else
3613 saveid = NULL;
3614
3615 char *previous_color_scheme = setColorScheme(agget(g, "colorscheme"));
3616 setup_page(job);
3621 && (obj->url || obj->explicit_tooltip)) {
3625 nump = 2;
3626 }
3627 else {
3629 nump = 4;
3630 }
3631 p = gv_calloc(nump, sizeof(pointf));
3632 p[0] = job->pageBox.LL;
3633 p[1] = job->pageBox.UR;
3635 rect2poly(p);
3636 }
3638 gvrender_ptf_A(job, p, p, nump);
3639 obj->url_map_p = p;
3640 obj->url_map_n = nump;
3641 }
3642 if ((flags & GVRENDER_DOES_LABELS) && ((lab = GD_label(g))))
3643 /* do graph label on every page and rely on clipping to show it on the right one(s) */
3644 obj->label = lab->text;
3645 /* If EMIT_CLUSTERS_LAST is set, we assume any URL or tooltip
3646 * attached to the root graph is emitted either in begin_page
3647 * or end_page of renderer.
3648 */
3649 if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip)) {
3650 emit_map_rect(job, job->clip);
3651 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3652 }
3653 emit_background(job, g);
3654 if (GD_label(g))
3656 if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip))
3658 emit_view(job,g,flags);
3659 gvrender_end_page(job);
3660 if (obj_id_needs_restore) {
3661 obj->id = saveid;
3662 }
3663 agxbfree(&xb);
3664
3665 char *color_scheme = setColorScheme(previous_color_scheme);
3666 free(color_scheme);
3667 free(previous_color_scheme);
3668}
3669
3670void emit_graph(GVJ_t * job, graph_t * g)
3671{
3672 node_t *n;
3673 char *s;
3674 int flags = job->flags;
3675 int* lp;
3676
3677 /* device dpi is now known */
3678 job->scale.x = job->zoom * job->dpi.x / POINTS_PER_INCH;
3679 job->scale.y = job->zoom * job->dpi.y / POINTS_PER_INCH;
3680
3681 job->devscale.x = job->dpi.x / POINTS_PER_INCH;
3682 job->devscale.y = job->dpi.y / POINTS_PER_INCH;
3683 if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
3684 job->devscale.y *= -1;
3685
3686 /* compute current view in graph units */
3687 if (job->rotation) {
3688 job->view.y = job->width / job->scale.y;
3689 job->view.x = job->height / job->scale.x;
3690 }
3691 else {
3692 job->view.x = job->width / job->scale.x;
3693 job->view.y = job->height / job->scale.y;
3694 }
3695
3696 s = late_string(g, agattr_text(g, AGRAPH, "comment", 0), "");
3697 gvrender_comment(job, s);
3698
3699 job->layerNum = 0;
3700 emit_begin_graph(job, g);
3701
3702 if (flags & EMIT_COLORS)
3703 emit_colors(job,g);
3704
3705 /* reset node state */
3706 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3707 ND_state(n) = 0;
3708 /* iterate layers */
3709 for (firstlayer(job,&lp); validlayer(job); nextlayer(job,&lp)) {
3710 if (numPhysicalLayers (job) > 1)
3712
3713 /* iterate pages */
3714 for (firstpage(job); validpage(job); nextpage(job))
3715 emit_page(job, g);
3716
3717 if (numPhysicalLayers (job) > 1)
3718 gvrender_end_layer(job);
3719 }
3720 emit_end_graph(job);
3721}
3722
3725 .link = -1, // link - allocate separate holder objects
3726 .freef = free,
3727};
3728
3729bool emit_once(char *str) {
3730 // use stderr as a mutual exclusion mechanism for `strings`
3731 lockfile(stderr);
3732
3733 if (strings == 0)
3735 if (!dtsearch(strings, str)) {
3737 unlockfile(stderr);
3738 return true;
3739 }
3740 unlockfile(stderr);
3741 return false;
3742}
3743
3745{
3746 // use stderr as a mutual exclusion mechanism for `strings`
3747 lockfile(stderr);
3748
3749 if (strings) {
3751 strings = 0;
3752 }
3753 unlockfile(stderr);
3754}
3755
3756static void emit_begin_cluster(GVJ_t * job, Agraph_t * sg)
3757{
3758 obj_state_t *obj;
3759
3760 obj = push_obj_state(job);
3761 obj->type = CLUSTER_OBJTYPE;
3762 obj->u.sg = sg;
3763 obj->emit_state = EMIT_CDRAW;
3764
3765 initObjMapData (job, GD_label(sg), sg);
3766
3768}
3769
3770static void emit_end_cluster(GVJ_t *job) {
3772 pop_obj_state(job);
3773}
3774
3775void emit_clusters(GVJ_t * job, Agraph_t * g, int flags)
3776{
3777 int doPerim, c, filled;
3778 pointf AF[4];
3779 char *color, *fillcolor, *pencolor, **style, *s;
3780 graph_t *sg;
3781 node_t *n;
3782 edge_t *e;
3783 obj_state_t *obj;
3784 textlabel_t *lab;
3785 int doAnchor;
3786 double penwidth;
3787
3788 for (c = 1; c <= GD_n_cluster(g); c++) {
3789 sg = GD_clust(g)[c];
3790 if (!clust_in_layer(job, sg))
3791 continue;
3792 /* when mapping, detect events on clusters after sub_clusters */
3794 emit_clusters(job, sg, flags);
3795 emit_begin_cluster(job, sg);
3796 obj = job->obj;
3797 doAnchor = obj->url || obj->explicit_tooltip;
3798 char *previous_color_scheme = setColorScheme(agget(sg, "colorscheme"));
3799 if (doAnchor && !(flags & EMIT_CLUSTERS_LAST)) {
3800 emit_map_rect(job, GD_bb(sg));
3801 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3802 }
3803 filled = 0;
3804 graphviz_polygon_style_t istyle = {0};
3805 if ((style = checkClusterStyle(sg, &istyle))) {
3806 gvrender_set_style(job, style);
3807 if (istyle.filled)
3808 filled = FILL;
3809 }
3810 fillcolor = pencolor = 0;
3811
3812 if (GD_gui_state(sg) & GUI_STATE_ACTIVE) {
3813 pencolor = DEFAULT_ACTIVEPENCOLOR;
3814 fillcolor = DEFAULT_ACTIVEFILLCOLOR;
3815 filled = FILL;
3816 }
3817 else if (GD_gui_state(sg) & GUI_STATE_SELECTED) {
3818 pencolor = DEFAULT_SELECTEDPENCOLOR;
3819 fillcolor = DEFAULT_SELECTEDFILLCOLOR;
3820 filled = FILL;
3821 }
3822 else if (GD_gui_state(sg) & GUI_STATE_DELETED) {
3823 pencolor = DEFAULT_DELETEDPENCOLOR;
3824 fillcolor = DEFAULT_DELETEDFILLCOLOR;
3825 filled = FILL;
3826 }
3827 else if (GD_gui_state(sg) & GUI_STATE_VISITED) {
3828 pencolor = DEFAULT_VISITEDPENCOLOR;
3829 fillcolor = DEFAULT_VISITEDFILLCOLOR;
3830 filled = FILL;
3831 }
3832 else {
3833 if ((color = agget(sg, "color")) != 0 && color[0])
3834 fillcolor = pencolor = color;
3835 if ((color = agget(sg, "pencolor")) != 0 && color[0])
3836 pencolor = color;
3837 if ((color = agget(sg, "fillcolor")) != 0 && color[0])
3838 fillcolor = color;
3839 /* bgcolor is supported for backward compatibility
3840 if fill is set, fillcolor trumps bgcolor, so
3841 don't bother checking.
3842 if gradient is set fillcolor trumps bgcolor
3843 */
3844 if ((filled == 0 || !fillcolor) && (color = agget(sg, "bgcolor")) != 0 && color[0]) {
3845 fillcolor = color;
3846 filled = FILL;
3847 }
3848
3849 }
3850 if (!pencolor) pencolor = DEFAULT_COLOR;
3851 if (!fillcolor) fillcolor = DEFAULT_FILL;
3852 char *clrs[2] = {0};
3853 if (filled != 0) {
3854 double frac;
3855 if (findStopColor (fillcolor, clrs, &frac)) {
3856 gvrender_set_fillcolor(job, clrs[0]);
3857 if (clrs[1])
3858 gvrender_set_gradient_vals(job,clrs[1],late_int(sg,G_gradientangle,0,0), frac);
3859 else
3861 if (istyle.radial)
3862 filled = RGRADIENT;
3863 else
3864 filled = GRADIENT;
3865 }
3866 else
3867 gvrender_set_fillcolor(job, fillcolor);
3868 }
3869
3870 if (G_penwidth && ((s = agxget(sg, G_penwidth)) && s[0])) {
3871 penwidth = late_double(sg, G_penwidth, 1.0, 0.0);
3873 }
3874
3875 if (istyle.rounded) {
3876 if ((doPerim = late_int(sg, G_peripheries, 1, 0)) || filled != 0) {
3877 AF[0] = GD_bb(sg).LL;
3878 AF[2] = GD_bb(sg).UR;
3879 AF[1].x = AF[2].x;
3880 AF[1].y = AF[0].y;
3881 AF[3].x = AF[0].x;
3882 AF[3].y = AF[2].y;
3883 if (doPerim)
3884 gvrender_set_pencolor(job, pencolor);
3885 else
3886 gvrender_set_pencolor(job, "transparent");
3887 round_corners(job, AF, 4, istyle, filled);
3888 }
3889 }
3890 else if (istyle.striped) {
3891 AF[0] = GD_bb(sg).LL;
3892 AF[2] = GD_bb(sg).UR;
3893 AF[1].x = AF[2].x;
3894 AF[1].y = AF[0].y;
3895 AF[3].x = AF[0].x;
3896 AF[3].y = AF[2].y;
3897 if (late_int(sg, G_peripheries, 1, 0) == 0)
3898 gvrender_set_pencolor(job, "transparent");
3899 else
3900 gvrender_set_pencolor(job, pencolor);
3901 if (stripedBox (job, AF, fillcolor, 0) > 1)
3902 agerr (AGPREV, "in cluster %s\n", agnameof(sg));
3903 gvrender_box(job, GD_bb(sg), 0);
3904 }
3905 else {
3906 if (late_int(sg, G_peripheries, 1, 0)) {
3907 gvrender_set_pencolor(job, pencolor);
3908 gvrender_box(job, GD_bb(sg), filled);
3909 }
3910 else if (filled != 0) {
3911 gvrender_set_pencolor(job, "transparent");
3912 gvrender_box(job, GD_bb(sg), filled);
3913 }
3914 }
3915
3916 free (clrs[0]);
3917 free(clrs[1]);
3918 if ((lab = GD_label(sg)))
3919 emit_label(job, EMIT_CLABEL, lab);
3920
3921 if (doAnchor) {
3922 if (flags & EMIT_CLUSTERS_LAST) {
3923 emit_map_rect(job, GD_bb(sg));
3924 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3925 }
3927 }
3928
3929 if (flags & EMIT_PREORDER) {
3930 for (n = agfstnode(sg); n; n = agnxtnode(sg, n)) {
3931 emit_node(job, n);
3932 for (e = agfstout(sg, n); e; e = agnxtout(sg, e))
3933 emit_edge(job, e);
3934 }
3935 }
3936 emit_end_cluster(job);
3937 /* when drawing, lay down clusters before sub_clusters */
3938 if (!(flags & EMIT_CLUSTERS_LAST))
3939 emit_clusters(job, sg, flags);
3940
3941 char *color_scheme = setColorScheme(previous_color_scheme);
3942 free(color_scheme);
3943 free(previous_color_scheme);
3944 }
3945}
3946
3947static bool is_style_delim(int c)
3948{
3949 switch (c) {
3950 case '(':
3951 case ')':
3952 case ',':
3953 case '\0':
3954 return true;
3955 default:
3956 return false;
3957 }
3958}
3959
3960#define SID 1
3961
3966typedef struct {
3967 int type;
3968 const char *start;
3969 size_t size;
3970} token_t;
3971
3972static token_t style_token(char **s) {
3973 char *p = *s;
3974 int token;
3975
3976 while (gv_isspace(*p) || *p == ',')
3977 p++;
3978 const char *start = p;
3979 switch (*p) {
3980 case '\0':
3981 token = 0;
3982 break;
3983 case '(':
3984 case ')':
3985 token = *p++;
3986 break;
3987 default:
3988 token = SID;
3989 while (!is_style_delim(*p)) {
3990 p++;
3991 }
3992 }
3993 *s = p;
3994 assert(start <= p);
3995 size_t size = (size_t)(p - start);
3996 return (token_t){.type = token, .start = start, .size = size};
3997}
3998
3999#define FUNLIMIT 64
4000
4001/* This is one of the worst internal designs in graphviz.
4002 * The use of '\0' characters within strings seems cute but it
4003 * makes all of the standard functions useless if not dangerous.
4004 * Plus the function uses static memory for both the array and
4005 * the character buffer. One hopes all of the values are used
4006 * before the function is called again.
4007 */
4008char **parse_style(char *s)
4009{
4010 static char *parse[FUNLIMIT];
4011 size_t parse_offsets[sizeof(parse) / sizeof(parse[0])];
4012 size_t fun = 0;
4013 bool in_parens = false;
4014 char *p;
4015 static agxbuf ps_xb;
4016
4017 p = s;
4018 while (true) {
4019 token_t c = style_token(&p);
4020 if (c.type == 0) {
4021 break;
4022 }
4023 switch (c.type) {
4024 case '(':
4025 if (in_parens) {
4026 agerrorf("nesting not allowed in style: %s\n", s);
4027 parse[0] = NULL;
4028 return parse;
4029 }
4030 in_parens = true;
4031 break;
4032
4033 case ')':
4034 if (!in_parens) {
4035 agerrorf("unmatched ')' in style: %s\n", s);
4036 parse[0] = NULL;
4037 return parse;
4038 }
4039 in_parens = false;
4040 break;
4041
4042 default:
4043 if (!in_parens) {
4044 if (fun == FUNLIMIT - 1) {
4045 agwarningf("truncating style '%s'\n", s);
4046 parse[fun] = NULL;
4047 return parse;
4048 }
4049 agxbputc(&ps_xb, '\0'); /* terminate previous */
4050 parse_offsets[fun++] = agxblen(&ps_xb);
4051 }
4052 agxbput_n(&ps_xb, c.start, c.size);
4053 agxbputc(&ps_xb, '\0');
4054 }
4055 }
4056
4057 if (in_parens) {
4058 agerrorf("unmatched '(' in style: %s\n", s);
4059 parse[0] = NULL;
4060 return parse;
4061 }
4062
4063 char *base = agxbuse(&ps_xb); // add final '\0' to buffer
4064
4065 // construct list of style strings
4066 for (size_t i = 0; i < fun; ++i) {
4067 parse[i] = base + parse_offsets[i];
4068 }
4069 parse[fun] = NULL;
4070
4071 return parse;
4072}
4073
4075{
4076 pointf p, p1, p2;
4077 boxf bb;
4078
4079 assert(bz.size > 0);
4080 assert(bz.size % 3 == 1);
4081 bb.LL = bb.UR = bz.list[0];
4082 for (size_t i = 1; i < bz.size;) {
4083 /* take mid-point between two control points for bb calculation */
4084 p1=bz.list[i];
4085 i++;
4086 p2=bz.list[i];
4087 i++;
4088 p.x = ( p1.x + p2.x ) / 2;
4089 p.y = ( p1.y + p2.y ) / 2;
4090 expandbp(&bb, p);
4091
4092 p=bz.list[i];
4093 expandbp(&bb, p);
4094 i++;
4095 }
4096 return bb;
4097}
4098
4099static void init_splines_bb(splines *spl)
4100{
4101 bezier bz;
4102 boxf bb, b;
4103
4104 assert(spl->size > 0);
4105 bz = spl->list[0];
4106 bb = bezier_bb(bz);
4107 for (size_t i = 0; i < spl->size; i++) {
4108 if (i > 0) {
4109 bz = spl->list[i];
4110 b = bezier_bb(bz);
4111 EXPANDBB(&bb, b);
4112 }
4113 if (bz.sflag) {
4114 b = arrow_bb(bz.sp, bz.list[0], 1);
4115 EXPANDBB(&bb, b);
4116 }
4117 if (bz.eflag) {
4118 b = arrow_bb(bz.ep, bz.list[bz.size - 1], 1);
4119 EXPANDBB(&bb, b);
4120 }
4121 }
4122 spl->bb = bb;
4123}
4124
4125static void init_bb_edge(edge_t *e)
4126{
4127 splines *spl;
4128
4129 spl = ED_spl(e);
4130 if (spl)
4131 init_splines_bb(spl);
4132}
4133
4134static void init_bb_node(graph_t *g, node_t *n)
4135{
4136 edge_t *e;
4137
4138 ND_bb(n).LL.x = ND_coord(n).x - ND_lw(n);
4139 ND_bb(n).LL.y = ND_coord(n).y - ND_ht(n) / 2.;
4140 ND_bb(n).UR.x = ND_coord(n).x + ND_rw(n);
4141 ND_bb(n).UR.y = ND_coord(n).y + ND_ht(n) / 2.;
4142
4143 for (e = agfstout(g, n); e; e = agnxtout(g, e))
4144 init_bb_edge(e);
4145
4146 /* IDEA - could also save in the node the bb of the node and
4147 all of its outedges, then the scan time would be proportional
4148 to just the number of nodes for many graphs.
4149 Wouldn't work so well if the edges are sprawling all over the place
4150 because then the boxes would overlap a lot and require more tests,
4151 but perhaps that wouldn't add much to the cost before trying individual
4152 nodes and edges. */
4153}
4154
4155static void init_bb(graph_t *g)
4156{
4157 node_t *n;
4158
4159 for (n = agfstnode(g); n; n = agnxtnode(g, n))
4160 init_bb_node(g, n);
4161}
4162
4164extern const size_t gvevent_key_binding_size;
4166
4167/* Set LC_NUMERIC to "C" to get expected interpretation of %f
4168 * in printf functions. Languages like postscript and dot expect
4169 * floating point numbers to use a decimal point.
4170 *
4171 * If set is non-zero, the "C" locale set;
4172 * if set is zero, the original locale is reset.
4173 * Calls to the function can nest.
4174 */
4175void gv_fixLocale (int set)
4176{
4177 static char* save_locale;
4178 static int cnt;
4179
4180 if (set) {
4181 cnt++;
4182 if (cnt == 1) {
4183 save_locale = gv_strdup(setlocale (LC_NUMERIC, NULL));
4184 setlocale (LC_NUMERIC, "C");
4185 }
4186 }
4187 else if (cnt > 0) {
4188 cnt--;
4189 if (cnt == 0) {
4190 setlocale (LC_NUMERIC, save_locale);
4191 free (save_locale);
4192 }
4193 }
4194}
4195
4196
4197#define FINISH() \
4198 GV_DEBUG("gvRenderJobs %s: %.2f secs.", agnameof(g), elapsed_sec())
4199
4201{
4202 static GVJ_t *prevjob;
4203 GVJ_t *job, *firstjob;
4204
4205 if (Verbose)
4206 start_timer();
4207
4208 if (!LAYOUT_DONE(g)) {
4209 agerrorf("Layout was not done. Missing layout plugins? \n");
4210 FINISH();
4211 return -1;
4212 }
4213
4214 init_bb(g);
4215 init_gvc(gvc, g);
4216 init_layering(gvc, g);
4217
4218 gv_fixLocale (1);
4219 for (job = gvjobs_first(gvc); job; job = gvjobs_next(gvc)) {
4220 if (gvc->gvg) {
4222 job->graph_index = gvc->gvg->graph_index;
4223 }
4224 else {
4225 job->input_filename = NULL;
4226 job->graph_index = 0;
4227 }
4228 job->common = &gvc->common;
4229 job->layout_type = gvc->layout.type;
4232 if (!GD_drawing(g)) {
4233 agerrorf("layout was not done\n");
4234 gv_fixLocale (0);
4235 FINISH();
4236 return -1;
4237 }
4238
4240 if (job->output_lang == NO_SUPPORT) {
4241 agerrorf("renderer for %s is unavailable\n", job->output_langname);
4242 gv_fixLocale (0);
4243 FINISH();
4244 return -1;
4245 }
4246
4247 switch (job->output_lang) {
4248 case VTX:
4249 /* output sorted, i.e. all nodes then all edges */
4250 job->flags |= EMIT_SORTED;
4251 break;
4252 default:
4253 job->flags |= chkOrder(g);
4254 break;
4255 }
4256
4257 // if we already have an active job list and the device doesn't support
4258 // multiple output files, or we are about to write to a different output
4259 // device
4260 firstjob = gvc->active_jobs;
4261 if (firstjob) {
4262 if (! (firstjob->flags & GVDEVICE_DOES_PAGES)
4263 || strcmp(job->output_langname, firstjob->output_langname)) {
4264
4265 gvrender_end_job(firstjob);
4266
4267 gvc->active_jobs = NULL; /* clear active list */
4268 gvc->common.viewNum = 0;
4269 prevjob = NULL;
4270 }
4271 }
4272 else {
4273 prevjob = NULL;
4274 }
4275
4276 if (prevjob) {
4277 prevjob->next_active = job; /* insert job in active list */
4278 job->output_file = prevjob->output_file; /* FIXME - this is dumb ! */
4279 }
4280 else {
4281 if (gvrender_begin_job(job))
4282 continue;
4283 gvc->active_jobs = job; /* first job of new list */
4284 }
4285 job->next_active = NULL; /* terminate active list */
4287
4288 init_job_pad(job);
4289 init_job_margin(job);
4290 init_job_dpi(job, g);
4291 init_job_viewport(job, g);
4292 init_job_pagination(job, g);
4293
4294 if (! (job->flags & GVDEVICE_EVENTS)) {
4295 if (debug) {
4296 // Show_boxes is not defined, if at all, until splines are generated in dot
4299// FIXME: remove the cast and change `show_boxes` to a `char **` at the next API
4300// break
4301#ifdef __GNUC__
4302#pragma GCC diagnostic push
4303#pragma GCC diagnostic ignored "-Wcast-qual"
4304#endif
4305 job->common->show_boxes = (const char **)LIST_FRONT(&Show_boxes);
4306#ifdef __GNUC__
4307#pragma GCC diagnostic pop
4308#endif
4309 }
4310 emit_graph(job, g);
4311 }
4312
4313 /* the last job, after all input graphs are processed,
4314 * is finalized from gvFinalize()
4315 */
4316 prevjob = job;
4317 }
4318 gv_fixLocale (0);
4319 FINISH();
4320 return 0;
4321}
4322
4323/* Check for colon in colorlist. If one exists, and not the first
4324 * character, store the characters before the colon in clrs[0] and
4325 * the characters after the colon (and before the next or end-of-string)
4326 * in clrs[1]. If there are no characters after the first colon, clrs[1]
4327 * is NULL. Return TRUE.
4328 * If there is no non-trivial string before a first colon, set clrs[0] to
4329 * NULL and return FALSE.
4330 *
4331 * Note that memory for clrs must be freed by calling function.
4332 */
4333bool findStopColor(const char *colorlist, char *clrs[2], double *frac) {
4334 colorsegs_t segs = {.dtor = freeSeg};
4335 int rv;
4336 clrs[0] = NULL;
4337 clrs[1] = NULL;
4338
4339 rv = parseSegs(colorlist, &segs);
4340 if (rv || LIST_SIZE(&segs) < 2 || LIST_FRONT(&segs)->color == NULL) {
4341 LIST_FREE(&segs);
4342 return false;
4343 }
4344
4345 if (LIST_SIZE(&segs) > 2)
4346 agwarningf("More than 2 colors specified for a gradient - ignoring remaining\n");
4347
4348 clrs[0] = gv_strdup(LIST_FRONT(&segs)->color);
4349 if (LIST_GET(&segs, 1).color) {
4350 clrs[1] = gv_strdup(LIST_GET(&segs, 1).color);
4351 }
4352
4353 if (LIST_FRONT(&segs)->hasFraction)
4354 *frac = LIST_FRONT(&segs)->t;
4355 else if (LIST_GET(&segs, 1).hasFraction)
4356 *frac = 1 - LIST_GET(&segs, 1).t;
4357 else
4358 *frac = 0;
4359
4360 LIST_FREE(&segs);
4361 return true;
4362}
4363
static agxbuf last
last message
Definition agerror.c:29
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:1112
void arrow_gen(GVJ_t *job, emit_state_t emit_state, pointf p, pointf u, double arrowsize, double penwidth, uint32_t flag)
Definition arrows.c:1150
container data types API
#define dtsearch(d, o)
Definition cdt.h:184
#define dtinsert(d, o)
Definition cdt.h:186
CDT_API int dtclose(Dt_t *)
Definition dtclose.c:8
CDT_API Dtmethod_t * Dtoset
ordered set (self-adjusting tree)
Definition dttree.c:304
CDT_API Dt_t * dtopen(Dtdisc_t *, Dtmethod_t *)
Definition dtopen.c:9
#define parent(i)
Definition closest.c:73
#define right(i)
Definition closest.c:72
COLORPROCS_API char * setColorScheme(const char *s)
Definition colxlate.c:395
char * late_nnstring(void *obj, attrsym_t *attr, char *defaultValue)
Definition utils.c:88
bool mapbool(const char *p)
Definition utils.c:339
char * late_string(void *obj, attrsym_t *attr, char *defaultValue)
Definition utils.c:82
int late_int(void *obj, attrsym_t *attr, int defaultValue, int minimum)
Definition utils.c:37
pointf Bezier(const pointf *V, double t, pointf *Left, pointf *Right)
Definition utils.c:172
bool overlap_label(textlabel_t *lp, boxf b)
Definition utils.c:1320
double late_double(void *obj, attrsym_t *attr, double defaultValue, double minimum)
Definition utils.c:52
pointf dotneato_closest(splines *spl, pointf pt)
Definition utils.c:344
char * latin1ToUTF8(char *s)
Converts string from Latin1 encoding to utf8. Also translates HTML entities.
Definition utils.c:1257
char * htmlEntityUTF8(char *s, graph_t *g)
Definition utils.c:1180
#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:40
static float dx
Definition draw.c:39
#define left
Definition dthdr.h:12
static void ins(Dict_t *d, Dtlink_t **set, Agedge_t *e)
Definition edge.c:149
static void del(Dict_t *d, Dtlink_t **set, Agedge_t *e)
Definition edge.c:156
Ppolyline_t * ellipticWedge(pointf ctr, double xsemi, double ysemi, double angle0, double angle1)
Definition ellipse.c:297
#define EPSILON
Definition emit.c: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:2301
bool emit_once(char *str)
Definition emit.c:3729
static size_t find_prev_distinct(const pointf *pts, size_t i)
Definition emit.c:2103
static bool node_in_box(node_t *n, boxf b)
Definition emit.c:1635
static bool write_edge_test(Agraph_t *g, Agedge_t *e)
Definition emit.c:1326
static void free_stroke(stroke_t sp)
Definition emit.c:2055
static void map_point(GVJ_t *job, pointf pf)
Definition emit.c:342
static radfunc_t taperfun(edge_t *e)
Definition emit.c:2087
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:1361
static void emit_end_graph(GVJ_t *job)
Definition emit.c:3583
static void emit_end_cluster(GVJ_t *job)
Definition emit.c:3770
static int compare_corners(const void *a, const void *b)
Definition emit.c:2142
static void emit_edge_graphics(GVJ_t *job, edge_t *e, char **styles)
Definition emit.c:2352
void emit_clusters(GVJ_t *job, Agraph_t *g, int flags)
Definition emit.c:3775
static Dict_t * strings
Definition emit.c:3723
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:2066
static void nodeIntersect(GVJ_t *job, pointf p, bool explicit_iurl, char *iurl, bool explicit_itooltip)
Definition emit.c:2947
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:2154
static char * defaultlinestyle[3]
Definition emit.c:105
static void init_splines_bb(splines *spl)
Definition emit.c:4099
void gv_fixLocale(int set)
Definition emit.c:4175
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:4155
static void emit_end_node(GVJ_t *job)
Definition emit.c:1784
static pointf computeoffset_qr(pointf p, pointf q, pointf r, pointf s, double d)
Definition emit.c:1848
static bool edge_in_layer(GVJ_t *job, edge_t *e)
Definition emit.c:1603
@ debug
Definition emit.c:60
static double forfunc(double curlen, double totallen, double initwid)
Definition emit.c:2061
#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:1475
static void init_bb_edge(edge_t *e)
Definition emit.c:4125
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:4074
static bool is_style_delim(int c)
Definition emit.c:3947
static void expandBB(boxf *bb, pointf p)
Definition emit.c:3083
static void find_ortho_corners(const pointf *pts, size_t n, double radius, corners_t *corners)
Definition emit.c:2262
bool findStopColor(const char *colorlist, char *clrs[2], double *frac)
Definition emit.c:4333
static bool clust_in_layer(GVJ_t *job, graph_t *sg)
Definition emit.c:1620
static bool isRect(polygon_t *p)
Definition emit.c:695
static void emit_edge(GVJ_t *job, edge_t *e)
Definition emit.c:3037
static const char adjust[]
Definition emit.c:3080
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:3724
static void init_bb_node(graph_t *g, node_t *n)
Definition emit.c:4134
#define NotFirstPage(j)
Definition emit.c:3589
static double bothfunc(double curlen, double totallen, double initwid)
Definition emit.c:2079
static int multicolor(GVJ_t *job, edge_t *e, char **styles, const char *colors, double arrowsize, double penwidth)
Definition emit.c:1977
#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:3999
static void draw_ortho_corner_markers(GVJ_t *job, const corners_t *corners, double radius, char *edge_color)
Definition emit.c:2326
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:3960
#define THIN_LINE
Definition emit.c:538
static boxf textBB(double x, double y, textspan_t *span)
Definition emit.c:3114
static void emit_begin_node(GVJ_t *job, node_t *n)
Definition emit.c:1642
static double approxLen(pointf *pts)
Definition emit.c:1907
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:1584
static void firstpage(GVJ_t *job)
Definition emit.c:1301
static void splitBSpline(bezier *bz, double t, bezier *left, bezier *right)
Definition emit.c:1923
static char * saved_color_scheme
Definition emit.c:1640
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:2888
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:1306
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:3756
static void setup_page(GVJ_t *job)
Definition emit.c:1531
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:1869
#define FINISH()
Definition emit.c:4197
static void emit_page(GVJ_t *job, graph_t *g)
Definition emit.c:3591
gvevent_key_binding_t gvevent_key_binding[]
Definition gvevent.c:521
char ** parse_style(char *s)
Definition emit.c:4008
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:1897
void emit_once_reset(void)
Definition emit.c:3744
void emit_map_rect(GVJ_t *job, boxf b)
Definition emit.c:640
static void emit_end_edge(GVJ_t *job)
Definition emit.c:2970
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:2071
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:3091
void pop_obj_state(GVJ_t *job)
Definition emit.c:132
static void nextpage(GVJ_t *job)
Definition emit.c:1314
static pointf * copyPts(xdot_point *inpts, size_t numpts)
Definition emit.c:1352
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:2692
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:2712
static token_t style_token(char **s)
Definition emit.c:3972
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:1339
static void emit_node(GVJ_t *job, node_t *n)
Definition emit.c:1796
#define P2RECT(p, pr, sx, sy)
Definition emit.c:53
void emit_graph(GVJ_t *job, graph_t *g)
Definition emit.c:3670
boxf xdotBB(Agraph_t *g)
Definition emit.c:3144
double(* radfunc_t)(double, double, double)
Definition emit.c:2059
static size_t find_next_distinct(const pointf *pts, size_t i, size_t n)
Definition emit.c:2119
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:1835
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:61
void rect2poly(pointf *p)
Definition geom.c:122
double ptToLine2(pointf a, pointf b, pointf p)
Definition geom.c:181
#define POINTS(a_inches)
Definition geom.h:62
struct pointf_s pointf
#define DIST(p, q)
Definition geom.h:56
#define POINTS_PER_INCH
Definition geom.h:58
geometric functions (e.g. on points and boxes)
static 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:136
static bool Verbose
Definition gml2gv.c:24
void free(void *)
#define FILL
Definition gmlparse.h:119
edge
Definition gmlparse.y:244
#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:196
Agsym_t * agattr_text(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up text attributes of a graph
Definition attr.c:334
char * agget(void *obj, char *name)
Definition attr.c:448
char * agxget(void *obj, Agsym_t *sym)
Definition attr.c:458
#define ED_xlabel(e)
Definition types.h:590
#define ED_head_label(e)
Definition types.h:587
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:26
#define ED_gui_state(e)
Definition types.h:586
#define ED_spl(e)
Definition types.h:595
#define agtail(e)
Definition cgraph.h:977
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:96
#define ED_tail_label(e)
Definition types.h:596
#define aghead(e)
Definition cgraph.h:978
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:41
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:87
#define ED_label(e)
Definition types.h:589
void agwarningf(const char *fmt,...)
Definition agerror.c:173
void agerrorf(const char *fmt,...)
Definition agerror.c:165
int agerr(agerrlevel_t level, const char *fmt,...)
Definition agerror.c:155
@ AGPREV
Definition cgraph.h: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:176
#define GD_clust(g)
Definition types.h:360
#define GD_bb(g)
Definition types.h:354
#define GD_n_cluster(g)
Definition types.h:389
#define GD_label(g)
Definition types.h:374
#define GD_charset(g)
Definition types.h:367
#define GD_gvc(g)
Definition types.h:355
#define GD_odim(g)
Definition types.h:391
#define GD_gui_state(g)
Definition types.h:366
#define ND_ht(n)
Definition types.h:500
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:48
#define ND_bb(n)
Definition types.h:488
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:41
#define ND_state(n)
Definition types.h:531
#define ND_label(n)
Definition types.h:502
#define ND_rw(n)
Definition types.h:525
#define ND_lw(n)
Definition types.h:506
#define ND_xlabel(n)
Definition types.h:503
#define ND_shape_info(n)
Definition types.h:529
#define ND_pos(n)
Definition types.h:520
#define agfindnode(g, n)
Definition types.h:611
#define ND_coord(n)
Definition types.h:490
#define ND_shape(n)
Definition types.h:528
Agraph_t * agraphof(void *obj)
Definition obj.c:185
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:143
int agcontains(Agraph_t *, void *obj)
returns non-zero if obj is a member of (sub)graph
Definition obj.c:233
int agobjkind(void *obj)
Definition obj.c:252
Agraph_t * agroot(void *obj)
Definition obj.c:168
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
#define LAYOUT_DONE(g)
Definition gvc.h:52
int gvRenderJobs(GVC_t *gvc, graph_t *g)
Definition emit.c:4200
static uint64_t id
Definition gv2gml.c:40
static GVC_t * gvc
Definition gv.cpp:25
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:129
void gvrender_end_nodes(GVJ_t *job)
Definition gvrender.c:305
void gvrender_begin_nodes(GVJ_t *job)
Definition gvrender.c:295
void gvrender_end_job(GVJ_t *job)
Definition gvrender.c:116
void gvrender_beziercurve(GVJ_t *job, pointf *AF, size_t n, int filled)
Definition gvrender.c:579
void gvrender_comment(GVJ_t *job, char *str)
Definition gvrender.c:613
void gvrender_end_graph(GVJ_t *job)
Definition gvrender.c:225
void gvrender_set_style(GVJ_t *job, char **s)
Definition gvrender.c:481
void gvrender_set_fillcolor(GVJ_t *job, char *name)
Definition gvrender.c:450
void gvrender_polyline(GVJ_t *job, pointf *AF, size_t n)
Definition gvrender.c:596
void gvrender_begin_edges(GVJ_t *job)
Definition gvrender.c:315
pointf * gvrender_ptf_A(GVJ_t *job, pointf *af, pointf *AF, size_t n)
Definition gvrender.c:154
void gvrender_polygon(GVJ_t *job, pointf *af, size_t n, int filled)
Definition gvrender.c:537
void gvrender_end_edges(GVJ_t *job)
Definition gvrender.c:325
void gvrender_begin_graph(GVJ_t *job)
Definition gvrender.c:215
void gvrender_end_page(GVJ_t *job)
Definition gvrender.c:246
void gvrender_end_layer(GVJ_t *job)
Definition gvrender.c:267
void gvrender_box(GVJ_t *job, boxf BF, int filled)
Definition gvrender.c:565
void gvrender_begin_cluster(GVJ_t *job)
Definition gvrender.c:277
void gvrender_set_gradient_vals(GVJ_t *job, char *stopcolor, int angle, double frac)
Definition gvrender.c:467
void gvrender_ellipse(GVJ_t *job, pointf *AF, int filled)
Definition gvrender.c:520
void gvrender_end_edge(GVJ_t *job)
Definition gvrender.c:363
void gvrender_begin_anchor(GVJ_t *job, char *href, char *tooltip, char *target, char *id)
Definition gvrender.c:373
void gvrender_end_anchor(GVJ_t *job)
Definition gvrender.c:384
void gvrender_begin_layer(GVJ_t *job)
Definition gvrender.c:256
void gvrender_begin_page(GVJ_t *job)
Definition gvrender.c:236
void gvrender_begin_edge(GVJ_t *job)
Definition gvrender.c:354
GVJ_t * gvjobs_first(GVC_t *gvc)
Definition gvjobs.c:86
void gvrender_textspan(GVJ_t *job, pointf p, textspan_t *span)
Definition gvrender.c:414
void gvrender_begin_node(GVJ_t *job)
Definition gvrender.c:335
int gvrender_begin_job(GVJ_t *job)
Definition gvrender.c:103
void gvrender_end_cluster(GVJ_t *job)
Definition gvrender.c:286
GVJ_t * gvjobs_next(GVC_t *gvc)
Definition gvjobs.c:91
void gvrender_set_penwidth(GVJ_t *job, double penwidth)
Definition gvrender.c:798
int gvrender_select(GVJ_t *job, const char *lang)
Definition gvrender.c:40
void gvrender_end_node(GVJ_t *job)
Definition gvrender.c:344
void gvrender_set_pencolor(GVJ_t *job, char *name)
Definition gvrender.c:433
agxbput(xb, staging)
static xdot_state_t * xd
static double penwidth[]
$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:385
void free_textspan(textspan_t *tl, size_t cnt)
Definition labels.c:189
void emit_label(GVJ_t *job, emit_state_t emit_state, textlabel_t *lp)
Definition labels.c:215
type-generic dynamically expanding list
#define LIST_DETACH(list, datap, sizep)
Definition list.h:443
#define LIST_AT(list, index)
Definition list.h:168
#define LIST(type)
Definition list.h:55
#define LIST_BACK(list)
Definition list.h:191
#define LIST_SIZE(list)
Definition list.h:80
#define LIST_APPEND(list, item)
Definition list.h:120
#define LIST_DROP_BACK(list)
Definition list.h:422
#define LIST_FREE(list)
Definition list.h:370
#define LIST_RESERVE(list, capacity)
Definition list.h:257
#define LIST_SORT(list, cmp)
Definition list.h:338
#define LIST_FRONT(list)
Definition list.h:180
#define LIST_IS_EMPTY(list)
Definition list.h:90
#define LIST_SYNC(list)
Definition list.h:327
#define LIST_GET(list, index)
Definition list.h:155
platform abstraction over flockfile
static void unlockfile(FILE *file)
Definition lockfile.h:16
static void lockfile(FILE *file)
Definition lockfile.h:8
static int * ps
Definition lu.c:51
static void add_point(int *n, int igrp, double **x, int *nmax, double point[], int **groups)
Definition make_map.c:1205
#define delta
Definition maze.c:136
void freePath(Ppolyline_t *p)
Definition util.c:18
#define PRISIZE_T
Definition prisize_t.h:25
void round_corners(GVJ_t *job, pointf *AF, size_t sides, graphviz_polygon_style_t style, int filled)
Handle some special graphical cases, such as rounding the shape, adding diagonals at corners,...
Definition shapes.c:707
pointf textspan_size(GVC_t *gvc, textspan_t *span)
Estimates size of a textspan, in points.
Definition textspan.c:71
shape_kind shapeOf(node_t *)
Definition shapes.c:1906
pointf coord(node_t *n)
Definition utils.c:154
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
union _xdot_op::@106 u
xdot_font font
Definition xdot.h:157
xdot_polyline polygon
Definition xdot.h:150
xdot_rect ellipse
Definition xdot.h:149
char * color
Definition xdot.h:155
unsigned int fontchar
Definition xdot.h:159
xdot_kind kind
Definition xdot.h:147
xdot_text text
Definition xdot.h:153
xdot_polyline bezier
Definition xdot.h:152
xdot_polyline polyline
Definition xdot.h:151
xdot_color grad_color
Definition xdot.h:156
Definition types.h:89
size_t size
Definition types.h:91
pointf sp
Definition types.h:94
pointf * list
Definition types.h:90
uint32_t eflag
Definition types.h:93
pointf ep
Definition types.h:95
uint32_t sflag
Definition types.h:92
point LL
Definition geom.h:39
point UR
Definition geom.h:39
Definition geom.h:41
pointf UR
Definition geom.h:41
pointf LL
Definition geom.h:41
double t
segment size >= 0
Definition emit.c:413
char * color
Definition emit.c:412
bool hasFraction
Definition emit.c:414
pointf trunc_prev
Definition emit.c:2135
pointf trunc_next
Definition emit.c:2136
pointf wedge_center
Definition emit.c:2137
size_t idx
Definition emit.c:2134
double angle1
Definition emit.c:2138
double angle2
Definition emit.c:2138
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
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
union obj_state_s::@66 u
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:3968
int type
Token category.
Definition emit.c:3967
size_t size
Number of bytes in the token content.
Definition emit.c:3969
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::@105 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:21
void start_timer(void)
Definition timing.c:19
String tokenization.
static strview_t tok_get(const tok_t *t)
get the current token
Definition tokenize.h:76
static tok_t tok(const char *input, const char *separators)
begin tokenization of a new string
Definition tokenize.h:43
static bool tok_end(const tok_t *t)
is this tokenizer exhausted?
Definition tokenize.h:68
static void tok_next(tok_t *t)
advance to the next token in the string being scanned
Definition tokenize.h:85
#define GUI_STATE_ACTIVE
Definition types.h:256
@ SH_POINT
Definition types.h:187
@ SH_POLY
Definition types.h:187
#define GUI_STATE_SELECTED
Definition types.h:257
#define GUI_STATE_DELETED
Definition types.h:259
#define GUI_STATE_VISITED
Definition types.h:258
Definition grammar.c:90
#define UNREACHABLE()
Definition unreachable.h:30
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25
int(* pf)(void *, char *,...)
Definition xdot.c:396
int statXDot(xdot *x, xdot_stats *sp)
Definition xdot.c:778
xdot * parseXDotF(char *s, drawfunc_t fns[], size_t sz)
Definition xdot.c:390
parsing and deparsing of xdot operations
@ xd_radial
Definition xdot.h:46
@ xd_filled_polygon
Definition xdot.h:111
@ xd_pen_color
Definition xdot.h:118
@ xd_unfilled_ellipse
Definition xdot.h:110
@ xd_fontchar
Definition xdot.h:124
@ xd_font
Definition xdot.h:119
@ xd_fill_color
Definition xdot.h:117
@ xd_unfilled_bezier
Definition xdot.h:114
@ xd_grad_fill_color
Definition xdot.h:122
@ xd_polyline
Definition xdot.h:115
@ xd_text
Definition xdot.h:116
@ xd_filled_ellipse
Definition xdot.h:109
@ xd_image
Definition xdot.h:121
@ xd_unfilled_polygon
Definition xdot.h:112
@ xd_grad_pen_color
Definition xdot.h:123
@ xd_filled_bezier
Definition xdot.h:113
@ xd_style
Definition xdot.h:120