Graphviz 14.1.2~dev.20260120.0924
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 return DIST(pts[0], pts[1]) + DIST(pts[1], pts[2]) + DIST(pts[2], pts[3]);
1910}
1911
1912/* Given B-spline bz and 0 < t < 1, split bz so that left corresponds to
1913 * the fraction t of the arc length. The new parts are store in left and right.
1914 * The caller needs to free the allocated points.
1915 *
1916 * In the current implementation, we find the Bézier that should contain t by
1917 * treating the control points as a polyline.
1918 * We then split that Bézier.
1919 */
1920static void splitBSpline(bezier *bz, double t, bezier *left, bezier *right) {
1921 const size_t cnt = (bz->size - 1) / 3;
1922 double last, len, sum;
1923 pointf* pts;
1924
1925 if (cnt == 1) {
1926 left->size = 4;
1927 left->list = gv_calloc(4, sizeof(pointf));
1928 right->size = 4;
1929 right->list = gv_calloc(4, sizeof(pointf));
1930 Bezier (bz->list, t, left->list, right->list);
1931 return;
1932 }
1933
1934 double* lens = gv_calloc(cnt, sizeof(double));
1935 sum = 0;
1936 pts = bz->list;
1937 for (size_t i = 0; i < cnt; i++) {
1938 lens[i] = approxLen (pts);
1939 sum += lens[i];
1940 pts += 3;
1941 }
1942 len = t*sum;
1943 sum = 0;
1944 size_t i;
1945 for (i = 0; i < cnt; i++) {
1946 sum += lens[i];
1947 if (sum >= len)
1948 break;
1949 }
1950
1951 left->size = 3*(i+1) + 1;
1952 left->list = gv_calloc(left->size, sizeof(pointf));
1953 right->size = 3*(cnt-i) + 1;
1954 right->list = gv_calloc(right->size, sizeof(pointf));
1955 size_t j;
1956 for (j = 0; j < left->size; j++)
1957 left->list[j] = bz->list[j];
1958 size_t k = j - 4;
1959 for (j = 0; j < right->size; j++)
1960 right->list[j] = bz->list[k++];
1961
1962 last = lens[i];
1963 const double r = (len - (sum - last)) / last;
1964 Bezier (bz->list + 3*i, r, left->list + 3*i, right->list);
1965
1966 free (lens);
1967}
1968
1969/* Draw an edge as a sequence of colors.
1970 * Not sure how to handle multiple B-splines, so do a naive
1971 * implementation.
1972 * Return non-zero if color spec is incorrect
1973 */
1974static int multicolor(GVJ_t *job, edge_t *e, char **styles, const char *colors,
1975 double arrowsize, double penwidth) {
1976 bezier bz;
1977 bezier bz0, bz_l, bz_r;
1978 int rv;
1979 colorsegs_t segs;
1980 char* endcolor = NULL;
1981 double left;
1982 int first; /* first segment with t > 0 */
1983
1984 rv = parseSegs(colors, &segs);
1985 if (rv > 1) {
1986 Agraph_t* g = agraphof(agtail(e));
1987 agerr (AGPREV, "in edge %s%s%s\n", agnameof(agtail(e)), (agisdirected(g)?" -> ":" -- "), agnameof(aghead(e)));
1988
1989 if (rv == 2)
1990 return 1;
1991 }
1992 else if (rv == 1)
1993 return 1;
1994
1995
1996 for (size_t i = 0; i < ED_spl(e)->size; i++) {
1997 left = 1;
1998 bz = ED_spl(e)->list[i];
1999 first = 1;
2000 for (size_t j = 0; j < LIST_SIZE(&segs); ++j) {
2001 const colorseg_t s = LIST_GET(&segs, j);
2002 if (s.color == NULL) break;
2003 if (AEQ0(s.t)) continue;
2004 gvrender_set_pencolor(job, s.color);
2005 left -= s.t;
2006 endcolor = s.color;
2007 if (first) {
2008 first = 0;
2009 splitBSpline(&bz, s.t, &bz_l, &bz_r);
2010 gvrender_beziercurve(job, bz_l.list, bz_l.size, 0);
2011 free (bz_l.list);
2012 if (AEQ0(left)) {
2013 free (bz_r.list);
2014 break;
2015 }
2016 }
2017 else if (AEQ0(left)) {
2018 gvrender_beziercurve(job, bz_r.list, bz_r.size, 0);
2019 free (bz_r.list);
2020 break;
2021 }
2022 else {
2023 bz0 = bz_r;
2024 splitBSpline(&bz0, s.t / (left + s.t), &bz_l, &bz_r);
2025 free (bz0.list);
2026 gvrender_beziercurve(job, bz_l.list, bz_l.size, 0);
2027 free (bz_l.list);
2028 }
2029
2030 }
2031 /* arrow_gen resets the job style (How? FIXME)
2032 * If we have more splines to do, restore the old one.
2033 * Use local copy of penwidth to work around reset.
2034 */
2035 if (bz.sflag) {
2038 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2039 }
2040 if (bz.eflag) {
2041 gvrender_set_pencolor(job, endcolor);
2042 gvrender_set_fillcolor(job, endcolor);
2043 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2044 }
2045 if (ED_spl(e)->size > 1 && (bz.sflag || bz.eflag) && styles)
2046 gvrender_set_style(job, styles);
2047 }
2048 LIST_FREE(&segs);
2049 return 0;
2050}
2051
2052static void free_stroke(stroke_t sp) {
2053 free(sp.vertices);
2054}
2055
2056typedef double (*radfunc_t)(double,double,double);
2057
2058static double forfunc (double curlen, double totallen, double initwid)
2059{
2060 return (1 - curlen / totallen) * initwid / 2.0;
2061}
2062
2063static double revfunc (double curlen, double totallen, double initwid)
2064{
2065 return curlen / totallen * initwid / 2.0;
2066}
2067
2068static double nonefunc (double curlen, double totallen, double initwid)
2069{
2070 (void)curlen;
2071 (void)totallen;
2072
2073 return initwid / 2.0;
2074}
2075
2076static double bothfunc (double curlen, double totallen, double initwid)
2077{
2078 double fr = curlen/totallen;
2079 if (fr <= 0.5) return fr * initwid;
2080 return (1 - fr) * initwid;
2081}
2082
2083static radfunc_t
2085{
2086 char* attr;
2087 if (E_dir && ((attr = agxget(e, E_dir)))[0]) {
2088 if (streq(attr, "forward")) return forfunc;
2089 if (streq(attr, "back")) return revfunc;
2090 if (streq(attr, "both")) return bothfunc;
2091 if (streq(attr, "none")) return nonefunc;
2092 }
2094}
2095
2096/* find_prev_distinct:
2097 * Find the previous point that is not a duplicate of pts[i].
2098 * Returns `SIZE_MAX` if no such point exists.
2099 */
2100static size_t find_prev_distinct(const pointf *pts, size_t i) {
2101 const double TOLERANCE = 0.01;
2102 for (size_t j = i - 1; j != SIZE_MAX; j--) {
2103 double dx = pts[j].x - pts[i].x;
2104 double dy = pts[j].y - pts[i].y;
2105 if (hypot(dx, dy) > TOLERANCE) {
2106 return j;
2107 }
2108 }
2109 return SIZE_MAX;
2110}
2111
2112/* find_next_distinct:
2113 * Find the next point that is not a duplicate of pts[i].
2114 * Returns `SIZE_MAX` if no such point exists.
2115 */
2116static size_t find_next_distinct(const pointf *pts, size_t i, size_t n) {
2117 const double TOLERANCE = 0.01;
2118 for (size_t j = i + 1; j < n; j++) {
2119 double dx = pts[j].x - pts[i].x;
2120 double dy = pts[j].y - pts[i].y;
2121 if (hypot(dx, dy) > TOLERANCE) {
2122 return j;
2123 }
2124 }
2125 return SIZE_MAX;
2126}
2127
2128
2129/* Structure to hold corner information for truncation */
2130typedef struct {
2131 size_t idx; /* Index of corner point */
2132 pointf trunc_prev; /* Truncation point toward previous segment */
2133 pointf trunc_next; /* Truncation point toward next segment */
2134 pointf wedge_center; /* Center of arc wedge */
2135 double angle1, angle2; /* Arc angles */
2137
2138/* Comparison function for sorting corners by index */
2139static int compare_corners(const void *a, const void *b) {
2140 const corner_info_t *ca = a;
2141 const corner_info_t *cb = b;
2142 if (ca->idx < cb->idx) return -1;
2143 if (ca->idx > cb->idx) return 1;
2144 return 0;
2145}
2146
2147/* calculate_wedge_parameters:
2148 * Calculate the wedge center and angles for an orthogonal corner based on
2149 * the normalized direction vectors.
2150 */
2152 double dx1, double dy1, double dx2, double dy2,
2153 double radius, bool seg1_horiz, bool seg2_vert)
2154{
2155 if (seg1_horiz && seg2_vert) {
2156 if (dx1 > 0 && dy2 < 0) {
2157 /* Right then down */
2158 ci->wedge_center.x = curr.x - radius;
2159 ci->wedge_center.y = curr.y - radius;
2160 ci->angle1 = 0;
2161 ci->angle2 = M_PI / 2;
2162 } else if (dx1 > 0 && dy2 > 0) {
2163 /* Right then up */
2164 ci->wedge_center.x = curr.x - radius;
2165 ci->wedge_center.y = curr.y + radius;
2166 ci->angle1 = -M_PI / 2;
2167 ci->angle2 = 0;
2168 } else if (dx1 < 0 && dy2 < 0) {
2169 /* Left then down */
2170 ci->wedge_center.x = curr.x + radius;
2171 ci->wedge_center.y = curr.y - radius;
2172 ci->angle1 = M_PI / 2;
2173 ci->angle2 = M_PI;
2174 } else {
2175 /* Left then up */
2176 ci->wedge_center.x = curr.x + radius;
2177 ci->wedge_center.y = curr.y + radius;
2178 ci->angle1 = M_PI;
2179 ci->angle2 = 3 * M_PI / 2;
2180 }
2181 } else {
2182 if (dy1 < 0 && dx2 > 0) {
2183 /* Down then right */
2184 ci->wedge_center.x = curr.x + radius;
2185 ci->wedge_center.y = curr.y + radius;
2186 ci->angle1 = M_PI;
2187 ci->angle2 = 3 * M_PI / 2;
2188 } else if (dy1 < 0 && dx2 < 0) {
2189 /* Down then left */
2190 ci->wedge_center.x = curr.x - radius;
2191 ci->wedge_center.y = curr.y + radius;
2192 ci->angle1 = 3 * M_PI / 2;
2193 ci->angle2 = 2 * M_PI;
2194 } else if (dy1 > 0 && dx2 > 0) {
2195 /* Up then right */
2196 ci->wedge_center.x = curr.x + radius;
2197 ci->wedge_center.y = curr.y - radius;
2198 ci->angle1 = M_PI / 2;
2199 ci->angle2 = M_PI;
2200 } else {
2201 /* Up then left */
2202 ci->wedge_center.x = curr.x - radius;
2203 ci->wedge_center.y = curr.y - radius;
2204 ci->angle1 = 0;
2205 ci->angle2 = M_PI / 2;
2206 }
2207 }
2208}
2209
2210typedef LIST(corner_info_t) corners_t;
2211
2212/* process_corner:
2213 * Process a detected orthogonal corner and add it to the corners array.
2214 * Returns true if the corner was added, false if it was a duplicate.
2215 */
2216static bool process_corner(corners_t *corners, const pointf *pts, size_t i,
2217 pointf curr, double dx1, double dy1, double dx2,
2218 double dy2, double radius, bool seg1_horiz,
2219 bool seg2_vert) {
2220 /* Check if we already detected this corner (skip duplicates) */
2221 const double DUP_TOL = 0.01;
2222 for (size_t j = 0; j < LIST_SIZE(corners); j++) {
2223 const corner_info_t ci = LIST_GET(corners, j);
2224 pointf existing_corner = pts[ci.idx];
2225 if (hypot(curr.x - existing_corner.x, curr.y - existing_corner.y) < DUP_TOL) {
2226 return false;
2227 }
2228 }
2229
2230 LIST_APPEND(corners, (corner_info_t){0});
2231 corner_info_t *const ci = LIST_BACK(corners);
2232 ci->idx = i;
2233
2234 /* Normalize direction vectors */
2235 double len1 = hypot(dx1, dy1);
2236 double len2 = hypot(dx2, dy2);
2237 dx1 /= len1;
2238 dy1 /= len1;
2239 dx2 /= len2;
2240 dy2 /= len2;
2241
2242 /* Calculate truncation points (move radius distance away from corner) */
2243 ci->trunc_prev.x = curr.x - dx1 * radius;
2244 ci->trunc_prev.y = curr.y - dy1 * radius;
2245 ci->trunc_next.x = curr.x + dx2 * radius;
2246 ci->trunc_next.y = curr.y + dy2 * radius;
2247
2248 /* Calculate wedge center and angles */
2249 calculate_wedge_parameters(ci, curr, dx1, dy1, dx2, dy2, radius, seg1_horiz, seg2_vert);
2250
2251 return true;
2252}
2253
2254/* find_ortho_corners:
2255 * Identify all orthogonal corners and compute truncation/arc information.
2256 * Fills corners array.
2257 * Caller must pre-allocate corners array with at least n elements.
2258 */
2259static void find_ortho_corners(const pointf *pts, size_t n, double radius,
2260 corners_t *corners) {
2261 const double TOL = 0.1;
2262
2263 for (size_t i = 0; i < n; i++) {
2264 const size_t prev_idx = find_prev_distinct(pts, i);
2265 const size_t next_idx = find_next_distinct(pts, i, n);
2266
2267 if (prev_idx == SIZE_MAX || next_idx == SIZE_MAX) continue;
2268
2269 pointf prev = pts[prev_idx];
2270 pointf curr = pts[i];
2271 pointf next = pts[next_idx];
2272
2273 /* Get direction vectors */
2274 double dx1 = curr.x - prev.x;
2275 double dy1 = curr.y - prev.y;
2276 double dx2 = next.x - curr.x;
2277 double dy2 = next.y - curr.y;
2278
2279 /* Check if this is an orthogonal corner */
2280 bool seg1_horiz = fabs(dy1) < TOL && fabs(dx1) > TOL;
2281 bool seg1_vert = fabs(dx1) < TOL && fabs(dy1) > TOL;
2282 bool seg2_horiz = fabs(dy2) < TOL && fabs(dx2) > TOL;
2283 bool seg2_vert = fabs(dx2) < TOL && fabs(dy2) > TOL;
2284
2285 bool is_corner = (seg1_horiz && seg2_vert) || (seg1_vert && seg2_horiz);
2286
2287 if (is_corner) {
2288 process_corner(corners, pts, i, curr, dx1, dy1, dx2, dy2, radius,
2289 seg1_horiz, seg2_vert);
2290 }
2291 }
2292}
2293
2294/* render_corner_arc:
2295 * Extract and render the arc portion from a wedge polyline.
2296 * The wedge includes center point and return paths which we skip.
2297 */
2298static void render_corner_arc(GVJ_t *job, const Ppolyline_t *wedge,
2299 size_t arc_start_idx, size_t arc_point_count,
2300 char *edge_color)
2301{
2302 LIST(pointf) arc_points = {0};
2303 LIST_RESERVE(&arc_points, arc_point_count);
2304 for (size_t j = 0; j < arc_point_count; j++) {
2305 LIST_APPEND(&arc_points, wedge->ps[arc_start_idx + j]);
2306 }
2307
2308 /* Draw only the arc curve (no straight edges back to center) */
2309 if (edge_color && edge_color[0]) {
2310 gvrender_set_pencolor(job, edge_color);
2311 } else {
2313 }
2314 gvrender_polyline(job, LIST_FRONT(&arc_points), arc_point_count);
2315
2316 LIST_FREE(&arc_points);
2317}
2318
2319/* draw_ortho_corner_markers:
2320 * Draw 90-degree arc curves at orthogonal edge corners.
2321 * Draws only the arc portion (not the straight wedge edges).
2322 */
2323static void draw_ortho_corner_markers(GVJ_t *job, const corners_t *corners,
2324 double radius, char *edge_color) {
2325 for (size_t i = 0; i < LIST_SIZE(corners); i++) {
2326 const corner_info_t ci = LIST_GET(corners, i);
2327
2328 /* Generate wedge path */
2329 Ppolyline_t *wedge = ellipticWedge(ci.wedge_center, radius, radius,
2330 ci.angle1, ci.angle2);
2331
2332 if (wedge && wedge->pn > 4) {
2333 /* Extract only the arc portion (skip center at start and close path at end) */
2334 /* Skip duplicates and straight edges: wedge path includes center and return paths */
2335 size_t arc_start_idx = 3; /* Skip center, first arc point, AND duplicate */
2336 size_t arc_end_idx = wedge->pn - 4; /* Skip duplicate endpoint, last arc point, AND center */
2337 size_t arc_point_count = arc_end_idx >= arc_start_idx ? arc_end_idx - arc_start_idx + 1 : 0;
2338
2339 if (arc_point_count >= 2) {
2340 render_corner_arc(job, wedge, arc_start_idx, arc_point_count, edge_color);
2341 }
2342
2343 free(wedge->ps);
2344 free(wedge);
2345 }
2346 }
2347}
2348
2349static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
2350{
2351 int cnum, numsemi = 0;
2352 char *color, *pencolor, *fillcolor;
2353 char *headcolor, *tailcolor, *lastcolor;
2354 char *colors = NULL;
2355 bezier bz;
2356 splines offspl, tmpspl;
2357 pointf pf0, pf1, pf2 = { 0, 0 }, pf3, *offlist, *tmplist;
2358 double arrowsize, numc2, penwidth=job->obj->penwidth;
2359 char* p;
2360 bool tapered = false;
2361 agxbuf buf = {0};
2362
2363#define SEP 2.0
2364
2365 if (ED_spl(e)) {
2366 arrowsize = late_double(e, E_arrowsz, 1.0, 0.0);
2367 color = late_string(e, E_color, "");
2368
2369 if (styles) {
2370 char** sp = styles;
2371 while ((p = *sp++)) {
2372 if (streq(p, "tapered")) {
2373 tapered = true;
2374 break;
2375 }
2376 }
2377 }
2378
2379 /* need to know how many colors separated by ':' */
2380 size_t numc = 0;
2381 for (p = color; *p; p++) {
2382 if (*p == ':')
2383 numc++;
2384 else if (*p == ';')
2385 numsemi++;
2386 }
2387
2388 if (numsemi && numc) {
2389 if (multicolor(job, e, styles, color, arrowsize, penwidth)) {
2391 }
2392 else
2393 goto done;
2394 }
2395
2396 fillcolor = pencolor = color;
2397 if (ED_gui_state(e) & GUI_STATE_ACTIVE) {
2398 pencolor = default_pencolor(&buf, pencolor, DEFAULT_ACTIVEPENCOLOR);
2399 fillcolor = DEFAULT_ACTIVEFILLCOLOR;
2400 }
2401 else if (ED_gui_state(e) & GUI_STATE_SELECTED) {
2402 pencolor = default_pencolor(&buf, pencolor, DEFAULT_SELECTEDPENCOLOR);
2403 fillcolor = DEFAULT_SELECTEDFILLCOLOR;
2404 }
2405 else if (ED_gui_state(e) & GUI_STATE_DELETED) {
2406 pencolor = default_pencolor(&buf, pencolor, DEFAULT_DELETEDPENCOLOR);
2407 fillcolor = DEFAULT_DELETEDFILLCOLOR;
2408 }
2409 else if (ED_gui_state(e) & GUI_STATE_VISITED) {
2410 pencolor = default_pencolor(&buf, pencolor, DEFAULT_VISITEDPENCOLOR);
2411 fillcolor = DEFAULT_VISITEDFILLCOLOR;
2412 }
2413 else
2414 fillcolor = late_nnstring(e, E_fillcolor, color);
2415 if (pencolor != color)
2416 gvrender_set_pencolor(job, pencolor);
2417 if (fillcolor != color)
2418 gvrender_set_fillcolor(job, fillcolor);
2419 color = pencolor;
2420
2421 if (tapered) {
2422 if (*color == '\0') color = DEFAULT_COLOR;
2423 if (*fillcolor == '\0') fillcolor = DEFAULT_COLOR;
2424 gvrender_set_pencolor(job, "transparent");
2426 bz = ED_spl(e)->list[0];
2427 stroke_t stp = taper(&bz, taperfun (e), penwidth);
2428 assert(stp.nvertices <= INT_MAX);
2429 gvrender_polygon(job, stp.vertices, stp.nvertices, 1);
2430 free_stroke(stp);
2432 if (fillcolor != color)
2433 gvrender_set_fillcolor(job, fillcolor);
2434 if (bz.sflag) {
2435 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
2436 }
2437 if (bz.eflag) {
2438 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
2439 }
2440 }
2441 /* if more than one color - then generate parallel Béziers, one per color */
2442 else if (numc) {
2443 /* calculate and save offset vector spline and initialize first offset spline */
2444 tmpspl.size = offspl.size = ED_spl(e)->size;
2445 offspl.list = gv_calloc(offspl.size, sizeof(bezier));
2446 tmpspl.list = gv_calloc(tmpspl.size, sizeof(bezier));
2447 numc2 = (2 + (double)numc) / 2.0;
2448 for (size_t i = 0; i < offspl.size; i++) {
2449 bz = ED_spl(e)->list[i];
2450 tmpspl.list[i].size = offspl.list[i].size = bz.size;
2451 offlist = offspl.list[i].list = gv_calloc(bz.size, sizeof(pointf));
2452 tmplist = tmpspl.list[i].list = gv_calloc(bz.size, sizeof(pointf));
2453 pf3 = bz.list[0];
2454 size_t j;
2455 for (j = 0; j < bz.size - 1; j += 3) {
2456 pf0 = pf3;
2457 pf1 = bz.list[j + 1];
2458 /* calculate perpendicular vectors for each Bézier point */
2459 if (j == 0) /* first segment, no previous pf2 */
2460 offlist[j] = computeoffset_p(pf0, pf1, SEP);
2461 else /* i.e. pf2 is available from previous segment */
2462 offlist[j] = computeoffset_p(pf2, pf1, SEP);
2463 pf2 = bz.list[j + 2];
2464 pf3 = bz.list[j + 3];
2465 offlist[j + 1] = offlist[j + 2] =
2466 computeoffset_qr(pf0, pf1, pf2, pf3, SEP);
2467 /* initialize tmpspl to outermost position */
2468 tmplist[j].x = pf0.x - numc2 * offlist[j].x;
2469 tmplist[j].y = pf0.y - numc2 * offlist[j].y;
2470 tmplist[j + 1].x = pf1.x - numc2 * offlist[j + 1].x;
2471 tmplist[j + 1].y = pf1.y - numc2 * offlist[j + 1].y;
2472 tmplist[j + 2].x = pf2.x - numc2 * offlist[j + 2].x;
2473 tmplist[j + 2].y = pf2.y - numc2 * offlist[j + 2].y;
2474 }
2475 /* last segment, no next pf1 */
2476 offlist[j] = computeoffset_p(pf2, pf3, SEP);
2477 tmplist[j].x = pf3.x - numc2 * offlist[j].x;
2478 tmplist[j].y = pf3.y - numc2 * offlist[j].y;
2479 }
2480 lastcolor = headcolor = tailcolor = color;
2481 colors = gv_strdup(color);
2482 for (cnum = 0, color = strtok(colors, ":"); color;
2483 cnum++, color = strtok(0, ":")) {
2484 if (!color[0])
2486 if (color != lastcolor) {
2490 }
2491 lastcolor = color;
2492 }
2493 if (cnum == 0)
2494 headcolor = tailcolor = color;
2495 if (cnum == 1)
2496 tailcolor = color;
2497 for (size_t i = 0; i < tmpspl.size; i++) {
2498 tmplist = tmpspl.list[i].list;
2499 offlist = offspl.list[i].list;
2500 for (size_t j = 0; j < tmpspl.list[i].size; j++) {
2501 tmplist[j].x += offlist[j].x;
2502 tmplist[j].y += offlist[j].y;
2503 }
2504 gvrender_beziercurve(job, tmplist, tmpspl.list[i].size, 0);
2505 }
2506 }
2507 if (bz.sflag) {
2508 if (color != tailcolor) {
2509 color = tailcolor;
2513 }
2514 }
2515 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2516 arrowsize, penwidth, bz.sflag);
2517 }
2518 if (bz.eflag) {
2519 if (color != headcolor) {
2520 color = headcolor;
2524 }
2525 }
2526 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2527 arrowsize, penwidth, bz.eflag);
2528 }
2529 free(colors);
2530 for (size_t i = 0; i < offspl.size; i++) {
2531 free(offspl.list[i].list);
2532 free(tmpspl.list[i].list);
2533 }
2534 free(offspl.list);
2535 free(tmpspl.list);
2536 } else {
2538 if (color[0]) {
2540 gvrender_set_fillcolor(job, fillcolor);
2541 } else {
2543 if (fillcolor[0])
2544 gvrender_set_fillcolor(job, fillcolor);
2545 else
2547 }
2548 }
2549 for (size_t i = 0; i < ED_spl(e)->size; i++) {
2550 bz = ED_spl(e)->list[i];
2551
2552 /* Check if this edge has orthogonal routing and wants rounded corners */
2553 char *splines_attr = agget(agraphof(aghead(e)), "splines");
2554 bool is_ortho = splines_attr && streq(splines_attr, "ortho");
2555
2556 /* Check for rounded style or explicit radius attribute */
2557 bool want_rounded = false;
2558 double radius = 0.0;
2559
2560 /* First check if style=rounded */
2561 if (styles) {
2562 for (char **sp = styles; *sp; sp++) {
2563 if (streq(*sp, "rounded")) {
2564 want_rounded = true;
2565 break;
2566 }
2567 }
2568 }
2569
2570 /* Then check for explicit radius attribute (overrides style) */
2571 char *radius_attr = agget(e, "radius");
2572 if (radius_attr && radius_attr[0]) {
2573 radius = atof(radius_attr);
2574 want_rounded = radius > 0;
2575 }
2576
2577 /* If no explicit radius, use default when style=rounded */
2578 if (want_rounded && radius == 0.0) {
2579 radius = fmax(12.0, penwidth * 8.0);
2580 }
2581
2582 if (is_ortho && want_rounded && radius > 0) {
2583 /* Truncate edge at corners and draw arcs */
2584 corners_t corners = {0};
2585 LIST_RESERVE(&corners, bz.size);
2586 find_ortho_corners(bz.list, bz.size, radius, &corners);
2587
2588 if (!LIST_IS_EMPTY(&corners)) {
2589 /* Sort corners by index */
2590 LIST_SORT(&corners, compare_corners);
2591
2592 /* Render segments between corners */
2593 const double CORNER_TOL = 0.01;
2594 size_t seg_start_idx = 0;
2595 pointf seg_start_pt = bz.list[0];
2596
2597 for (size_t c = 0; c <= LIST_SIZE(&corners); c++) {
2598 size_t seg_end_idx;
2599 pointf seg_end_pt;
2600
2601 if (c < LIST_SIZE(&corners)) {
2602 /* Segment ends at this corner's trunc_prev */
2603 seg_end_idx = LIST_GET(&corners, c).idx;
2604 seg_end_pt = LIST_GET(&corners, c).trunc_prev;
2605 } else {
2606 /* Last segment ends at final point */
2607 seg_end_idx = bz.size - 1;
2608 seg_end_pt = bz.list[bz.size - 1];
2609 }
2610
2611 /* Build segment */
2612 LIST(pointf) seg_pts = {0};
2613 LIST_APPEND(&seg_pts, seg_start_pt);
2614 for (size_t pt = seg_start_idx + 1; pt < seg_end_idx; pt++) {
2615 bool at_corner = false;
2616 for (size_t cc = 0; cc < LIST_SIZE(&corners); cc++) {
2617 pointf corner_pt = bz.list[LIST_GET(&corners, cc).idx];
2618 if (hypot(bz.list[pt].x - corner_pt.x, bz.list[pt].y - corner_pt.y) < CORNER_TOL) {
2619 at_corner = true;
2620 break;
2621 }
2622 }
2623 if (!at_corner) {
2624 LIST_APPEND(&seg_pts, bz.list[pt]);
2625 }
2626 }
2627 LIST_APPEND(&seg_pts, seg_end_pt);
2628
2629 /* Render this segment as polyline (straight lines, not bezier) */
2630 gvrender_polyline(job, LIST_FRONT(&seg_pts), LIST_SIZE(&seg_pts));
2631 LIST_FREE(&seg_pts);
2632
2633 /* Prepare for next segment */
2634 if (c < LIST_SIZE(&corners)) {
2635 /* Skip all duplicates of this corner */
2636 size_t next_idx = LIST_GET(&corners, c).idx + 1;
2637 while (next_idx < bz.size) {
2638 bool at_corner = false;
2639 for (size_t cc = 0; cc < LIST_SIZE(&corners); cc++) {
2640 pointf corner_pt = bz.list[LIST_GET(&corners, cc).idx];
2641 if (hypot(bz.list[next_idx].x - corner_pt.x,
2642 bz.list[next_idx].y - corner_pt.y) < CORNER_TOL) {
2643 at_corner = true;
2644 break;
2645 }
2646 }
2647 if (!at_corner) break;
2648 next_idx++;
2649 }
2650 seg_start_idx = next_idx;
2651 seg_start_pt = LIST_GET(&corners, c).trunc_next;
2652 }
2653 }
2654
2655 /* Draw corner arcs to fill the gaps */
2656 draw_ortho_corner_markers(job, &corners, radius, color);
2657 } else {
2658 /* No corners found, render normally */
2659 gvrender_beziercurve(job, bz.list, bz.size, 0);
2660 }
2661 LIST_FREE(&corners);
2662 } else {
2663 /* Non-orthogonal edge, render normally */
2664 gvrender_beziercurve(job, bz.list, bz.size, 0);
2665 }
2666
2667 if (bz.sflag) {
2668 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
2669 arrowsize, penwidth, bz.sflag);
2670 }
2671 if (bz.eflag) {
2672 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
2673 arrowsize, penwidth, bz.eflag);
2674 }
2675 if (ED_spl(e)->size > 1 && (bz.sflag || bz.eflag) && styles)
2676 gvrender_set_style(job, styles);
2677 }
2678 }
2679 }
2680
2681done:
2682 agxbfree(&buf);
2683}
2684
2685static bool edge_in_box(edge_t *e, boxf b)
2686{
2687 splines *spl;
2688 textlabel_t *lp;
2689
2690 spl = ED_spl(e);
2691 if (spl && boxf_overlap(spl->bb, b))
2692 return true;
2693
2694 lp = ED_label(e);
2695 if (lp && overlap_label(lp, b))
2696 return true;
2697
2698 lp = ED_xlabel(e);
2699 if (lp && lp->set && overlap_label(lp, b))
2700 return true;
2701
2702 return false;
2703}
2704
2705static void emit_begin_edge(GVJ_t *job, edge_t *e, char **styles) {
2706 obj_state_t *obj;
2707 int flags = job->flags;
2708 char *s;
2709 textlabel_t *lab = NULL, *tlab = NULL, *hlab = NULL;
2710 char *dflt_url = NULL;
2711 char *dflt_target = NULL;
2712 double penwidth;
2713
2714 obj = push_obj_state(job);
2715 obj->type = EDGE_OBJTYPE;
2716 obj->u.e = e;
2717 obj->emit_state = EMIT_EDRAW;
2718 if (ED_label(e) && !ED_label(e)->html && mapbool(agget(e, "labelaligned")))
2719 obj->labeledgealigned = true;
2720
2721 /* We handle the edge style and penwidth here because the width
2722 * is needed below for calculating polygonal image maps
2723 */
2724 if (styles && ED_spl(e))
2725 gvrender_set_style(job, styles);
2726
2727 if (E_penwidth && (s = agxget(e, E_penwidth)) && s[0]) {
2728 penwidth = late_double(e, E_penwidth, 1.0, 0.0);
2730 }
2731
2732 if (flags & GVRENDER_DOES_Z) {
2733 if (GD_odim(agraphof(agtail(e))) >= 3) {
2734 obj->tail_z = POINTS(ND_pos(agtail(e))[2]);
2735 obj->head_z = POINTS(ND_pos(aghead(e))[2]);
2736 } else {
2737 obj->tail_z = obj->head_z = 0.0;
2738 }
2739 }
2740
2742 if ((lab = ED_label(e)))
2743 obj->label = lab->text;
2744 obj->taillabel = obj->headlabel = obj->xlabel = obj->label;
2745 if ((tlab = ED_xlabel(e)))
2746 obj->xlabel = tlab->text;
2747 if ((tlab = ED_tail_label(e)))
2748 obj->taillabel = tlab->text;
2749 if ((hlab = ED_head_label(e)))
2750 obj->headlabel = hlab->text;
2751 }
2752
2753 if (flags & GVRENDER_DOES_MAPS) {
2754 agxbuf xb = {0};
2755
2756 s = getObjId(job, e, &xb);
2757 obj->id = strdup_and_subst_obj(s, e);
2758 agxbfree(&xb);
2759
2760 if (((s = agget(e, "href")) && s[0]) || ((s = agget(e, "URL")) && s[0]))
2761 dflt_url = strdup_and_subst_obj(s, e);
2762 if (((s = agget(e, "edgehref")) && s[0]) ||
2763 ((s = agget(e, "edgeURL")) && s[0]))
2764 obj->url = strdup_and_subst_obj(s, e);
2765 else if (dflt_url)
2766 obj->url = gv_strdup(dflt_url);
2767 if (((s = agget(e, "labelhref")) && s[0]) ||
2768 ((s = agget(e, "labelURL")) && s[0]))
2769 obj->labelurl = strdup_and_subst_obj(s, e);
2770 else if (dflt_url)
2771 obj->labelurl = gv_strdup(dflt_url);
2772 if (((s = agget(e, "tailhref")) && s[0]) ||
2773 ((s = agget(e, "tailURL")) && s[0])) {
2774 obj->tailurl = strdup_and_subst_obj(s, e);
2775 obj->explicit_tailurl = true;
2776 } else if (dflt_url)
2777 obj->tailurl = gv_strdup(dflt_url);
2778 if (((s = agget(e, "headhref")) && s[0]) ||
2779 ((s = agget(e, "headURL")) && s[0])) {
2780 obj->headurl = strdup_and_subst_obj(s, e);
2781 obj->explicit_headurl = true;
2782 } else if (dflt_url)
2783 obj->headurl = gv_strdup(dflt_url);
2784 }
2785
2787 if ((s = agget(e, "target")) && s[0])
2788 dflt_target = strdup_and_subst_obj(s, e);
2789 if ((s = agget(e, "edgetarget")) && s[0]) {
2790 obj->explicit_edgetarget = true;
2791 obj->target = strdup_and_subst_obj(s, e);
2792 } else if (dflt_target)
2793 obj->target = gv_strdup(dflt_target);
2794 if ((s = agget(e, "labeltarget")) && s[0])
2796 else if (dflt_target)
2797 obj->labeltarget = gv_strdup(dflt_target);
2798 if ((s = agget(e, "tailtarget")) && s[0]) {
2800 obj->explicit_tailtarget = true;
2801 } else if (dflt_target)
2802 obj->tailtarget = gv_strdup(dflt_target);
2803 if ((s = agget(e, "headtarget")) && s[0]) {
2804 obj->explicit_headtarget = true;
2806 } else if (dflt_target)
2807 obj->headtarget = gv_strdup(dflt_target);
2808 }
2809
2811 if (((s = agget(e, "tooltip")) && s[0]) ||
2812 ((s = agget(e, "edgetooltip")) && s[0])) {
2813 char *tooltip = preprocessTooltip(s, e);
2814 obj->tooltip = strdup_and_subst_obj(tooltip, e);
2815 free(tooltip);
2816 obj->explicit_tooltip = true;
2817 } else if (obj->label)
2818 obj->tooltip = gv_strdup(obj->label);
2819
2820 if ((s = agget(e, "labeltooltip")) && s[0]) {
2821 char *tooltip = preprocessTooltip(s, e);
2822 obj->labeltooltip = strdup_and_subst_obj(tooltip, e);
2823 free(tooltip);
2824 obj->explicit_labeltooltip = true;
2825 } else if (obj->label)
2826 obj->labeltooltip = gv_strdup(obj->label);
2827
2828 if ((s = agget(e, "tailtooltip")) && s[0]) {
2829 char *tooltip = preprocessTooltip(s, e);
2830 obj->tailtooltip = strdup_and_subst_obj(tooltip, e);
2831 free(tooltip);
2832 obj->explicit_tailtooltip = true;
2833 } else if (obj->taillabel)
2834 obj->tailtooltip = gv_strdup(obj->taillabel);
2835
2836 if ((s = agget(e, "headtooltip")) && s[0]) {
2837 char *tooltip = preprocessTooltip(s, e);
2838 obj->headtooltip = strdup_and_subst_obj(tooltip, e);
2839 free(tooltip);
2840 obj->explicit_headtooltip = true;
2841 } else if (obj->headlabel)
2842 obj->headtooltip = gv_strdup(obj->headlabel);
2843 }
2844
2845 free(dflt_url);
2846 free(dflt_target);
2847
2849 if (ED_spl(e) && (obj->url || obj->tooltip) &&
2851 splines *spl;
2852 double w2 = fmax(job->obj->penwidth / 2.0, 2.0);
2853
2854 spl = ED_spl(e);
2855 const size_t ns = spl->size; /* number of splines */
2856 points_t pbs = {0};
2857 pbs_size_t pbs_n = {0};
2858 for (size_t i = 0; i < ns; i++)
2859 map_output_bspline(&pbs, &pbs_n, spl->list + i, w2);
2860 if (!(flags & GVRENDER_DOES_TRANSFORM)) {
2861 size_t nump = 0;
2862 for (size_t i = 0; i < LIST_SIZE(&pbs_n); ++i) {
2863 nump += LIST_GET(&pbs_n, i);
2864 }
2865 gvrender_ptf_A(job, LIST_FRONT(&pbs), LIST_FRONT(&pbs), nump);
2866 }
2867 obj->url_bsplinemap_p = LIST_FRONT(&pbs);
2869 LIST_DETACH(&pbs, &obj->url_map_p, NULL);
2870 obj->url_map_n = *LIST_FRONT(&pbs_n);
2872 }
2873 }
2874
2876 if (obj->url || obj->explicit_tooltip)
2877 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
2878}
2879
2880static void
2881emit_edge_label(GVJ_t* job, textlabel_t* lbl, emit_state_t lkind, int explicit,
2882 char* url, char* tooltip, char* target, char *id, splines* spl)
2883{
2884 int flags = job->flags;
2885 emit_state_t old_emit_state;
2886 char* newid;
2887 agxbuf xb = {0};
2888 char* type;
2889
2890 if (lbl == NULL || !lbl->set) return;
2891 if (id) { /* non-NULL if needed */
2892 switch (lkind) {
2893 case EMIT_ELABEL :
2894 type = "label";
2895 break;
2896 case EMIT_HLABEL :
2897 type = "headlabel";
2898 break;
2899 case EMIT_TLABEL :
2900 type = "taillabel";
2901 break;
2902 default :
2903 UNREACHABLE();
2904 }
2905 agxbprint(&xb, "%s-%s", id, type);
2906 newid = agxbuse(&xb);
2907 }
2908 else
2909 newid = NULL;
2910 old_emit_state = job->obj->emit_state;
2911 job->obj->emit_state = lkind;
2912 if ((url || explicit) && !(flags & EMIT_CLUSTERS_LAST)) {
2913 map_label(job, lbl);
2914 gvrender_begin_anchor(job, url, tooltip, target, newid);
2915 }
2916 emit_label(job, lkind, lbl);
2917 if (spl) emit_attachment(job, lbl, spl);
2918 if (url || explicit) {
2919 if (flags & EMIT_CLUSTERS_LAST) {
2920 map_label(job, lbl);
2921 gvrender_begin_anchor(job, url, tooltip, target, newid);
2922 }
2924 }
2925 agxbfree(&xb);
2926 job->obj->emit_state = old_emit_state;
2927}
2928
2929/* Common logic for setting hot spots at the beginning and end of
2930 * an edge.
2931 * If we are given a value (url, tooltip, target) explicitly set for
2932 * the head/tail, we use that.
2933 * Otherwise, if we are given a value explicitly set for the edge,
2934 * we use that.
2935 * Otherwise, we use whatever the argument value is.
2936 * We also note whether or not the tooltip was explicitly set.
2937 * If the url is non-NULL or the tooltip was explicit, we set
2938 * a hot spot around point p.
2939 */
2940static void nodeIntersect(GVJ_t *job, pointf p, bool explicit_iurl, char *iurl,
2941 bool explicit_itooltip) {
2942 obj_state_t *obj = job->obj;
2943 char* url;
2944 bool explicit;
2945
2946 if (explicit_iurl) url = iurl;
2947 else url = obj->url;
2948 if (explicit_itooltip) {
2949 explicit = true;
2950 }
2951 else if (obj->explicit_tooltip) {
2952 explicit = true;
2953 }
2954 else {
2955 explicit = false;
2956 }
2957
2958 if (url || explicit) {
2959 map_point(job, p);
2960 }
2961}
2962
2963static void emit_end_edge(GVJ_t * job)
2964{
2965 obj_state_t *obj = job->obj;
2966 edge_t *e = obj->u.e;
2967
2968 if (obj->url || obj->explicit_tooltip) {
2970 if (obj->url_bsplinemap_poly_n) {
2971 for (size_t nump = obj->url_bsplinemap_n[0], i = 1;
2972 i < obj->url_bsplinemap_poly_n; i++) {
2973 /* additional polygon maps around remaining Bézier pieces */
2974 obj->url_map_n = obj->url_bsplinemap_n[i];
2975 obj->url_map_p = &(obj->url_bsplinemap_p[nump]);
2977 obj->url, obj->tooltip, obj->target, obj->id);
2979 nump += obj->url_bsplinemap_n[i];
2980 }
2981 }
2982 }
2983 obj->url_map_n = 0; /* null out copy so that it doesn't get freed twice */
2984 obj->url_map_p = NULL;
2985
2986 if (ED_spl(e)) {
2987 pointf p;
2988 bezier bz;
2989
2990 /* process intersection with tail node */
2991 bz = ED_spl(e)->list[0];
2992 if (bz.sflag) /* Arrow at start of splines */
2993 p = bz.sp;
2994 else /* No arrow at start of splines */
2995 p = bz.list[0];
2996 nodeIntersect(job, p, obj->explicit_tailurl != 0, obj->tailurl,
2997 obj->explicit_tailtooltip != 0);
2998
2999 /* process intersection with head node */
3000 bz = ED_spl(e)->list[ED_spl(e)->size - 1];
3001 if (bz.eflag) /* Arrow at end of splines */
3002 p = bz.ep;
3003 else /* No arrow at end of splines */
3004 p = bz.list[bz.size - 1];
3005 nodeIntersect(job, p, obj->explicit_headurl != 0, obj->headurl,
3006 obj->explicit_headtooltip != 0);
3007 }
3008
3011 obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
3012 ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
3015 obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id,
3016 ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
3019 obj->headurl, obj->headtooltip, obj->headtarget, obj->id,
3020 0);
3023 obj->tailurl, obj->tailtooltip, obj->tailtarget, obj->id,
3024 0);
3025
3026 gvrender_end_edge(job);
3027 pop_obj_state(job);
3028}
3029
3030static void emit_edge(GVJ_t * job, edge_t * e)
3031{
3032 char *s;
3033 char *style;
3034 char **styles = NULL;
3035 char **sp;
3036 char *p;
3037
3038 if (edge_in_box(e, job->clip) && edge_in_layer(job, e) ) {
3039
3040 agxbuf edge = {0};
3041 agxbput(&edge, agnameof(agtail(e)));
3042 if (agisdirected(agraphof(aghead(e))))
3043 agxbput(&edge, "->");
3044 else
3045 agxbput(&edge, "--");
3046 agxbput(&edge, agnameof(aghead(e)));
3048 agxbfree(&edge);
3049
3050 s = late_string(e, E_comment, "");
3051 if (s[0])
3052 gvrender_comment(job, s);
3053
3054 style = late_string(e, E_style, "");
3055 /* We shortcircuit drawing an invisible edge because the arrowhead
3056 * code resets the style to solid, and most of the code generators
3057 * (except PostScript) won't honor a previous style of invis.
3058 */
3059 if (style[0]) {
3060 styles = parse_style(style);
3061 sp = styles;
3062 while ((p = *sp++)) {
3063 if (streq(p, "invis")) return;
3064 }
3065 }
3066
3067 char *const previous_color_scheme = setColorScheme(agget(e, "colorscheme"));
3068 emit_begin_edge(job, e, styles);
3069 emit_edge_graphics (job, e, styles);
3070 emit_end_edge(job);
3071 char *const our_color_scheme = setColorScheme(previous_color_scheme);
3072 free(previous_color_scheme);
3073 free(our_color_scheme);
3074 }
3075}
3076
3077static const char adjust[] = {'l', 'n', 'r'};
3078
3079static void
3081{
3082 bb->UR.x = fmax(bb->UR.x, p.x);
3083 bb->LL.x = fmin(bb->LL.x, p.x);
3084 bb->UR.y = fmax(bb->UR.y, p.y);
3085 bb->LL.y = fmin(bb->LL.y, p.y);
3086}
3087
3088static boxf ptsBB(xdot_point *inpts, size_t numpts, boxf *bb) {
3089 boxf opbb;
3090
3091 opbb.LL.x = opbb.UR.x = inpts->x;
3092 opbb.LL.y = opbb.UR.y = inpts->y;
3093 for (size_t i = 1; i < numpts; i++) {
3094 inpts++;
3095 if (inpts->x < opbb.LL.x)
3096 opbb.LL.x = inpts->x;
3097 else if (inpts->x > opbb.UR.x)
3098 opbb.UR.x = inpts->x;
3099 if (inpts->y < opbb.LL.y)
3100 opbb.LL.y = inpts->y;
3101 else if (inpts->y > opbb.UR.y)
3102 opbb.UR.y = inpts->y;
3103
3104 }
3105 expandBB (bb, opbb.LL);
3106 expandBB (bb, opbb.UR);
3107 return opbb;
3108}
3109
3110static boxf
3111textBB (double x, double y, textspan_t* span)
3112{
3113 boxf bb;
3114 pointf sz = span->size;
3115
3116 switch (span->just) {
3117 case 'l':
3118 bb.LL.x = x;
3119 bb.UR.x = bb.LL.x + sz.x;
3120 break;
3121 case 'n':
3122 bb.LL.x = x - sz.x / 2.0;
3123 bb.UR.x = x + sz.x / 2.0;
3124 break;
3125 case 'r':
3126 bb.UR.x = x;
3127 bb.LL.x = bb.UR.x - sz.x;
3128 break;
3129 }
3130 bb.UR.y = y + span->yoffset_layout;
3131 bb.LL.y = bb.UR.y - sz.y;
3132 return bb;
3133}
3134
3135static void freePara(xdot_op *xop) {
3136 exdot_op *const op = (exdot_op *)((char *)xop - offsetof(exdot_op, op));
3137 if (op->op.kind == xd_text)
3138 free_textspan (op->span, 1);
3139}
3140
3142{
3143 GVC_t *gvc = GD_gvc(g);
3144 exdot_op* op;
3145 double fontsize = 0.0;
3146 char* fontname = NULL;
3147 pointf pts[2];
3148 boxf bb0;
3149 boxf bb = GD_bb(g);
3150 xdot* xd = GD_drawing(g)->xdots;
3151 textfont_t tf, null_tf = {0};
3152 unsigned fontflags = 0;
3153
3154 if (!xd) return bb;
3155
3156 if (bb.LL.x == bb.UR.x && bb.LL.y == bb.UR.y) {
3157 bb.LL.x = bb.LL.y = DBL_MAX;
3158 bb.UR.x = bb.UR.y = -DBL_MAX;
3159 }
3160
3161 op = (exdot_op*)xd->ops;
3162 for (size_t i = 0; i < xd->cnt; i++) {
3163 tf = null_tf;
3164 switch (op->op.kind) {
3165 case xd_filled_ellipse :
3166 case xd_unfilled_ellipse :
3167 pts[0].x = op->op.u.ellipse.x - op->op.u.ellipse.w;
3168 pts[0].y = op->op.u.ellipse.y - op->op.u.ellipse.h;
3169 pts[1].x = op->op.u.ellipse.x + op->op.u.ellipse.w;
3170 pts[1].y = op->op.u.ellipse.y + op->op.u.ellipse.h;
3171 op->bb.LL = pts[0];
3172 op->bb.UR = pts[1];
3173 expandBB (&bb, pts[0]);
3174 expandBB (&bb, pts[1]);
3175 break;
3176 case xd_filled_polygon :
3177 case xd_unfilled_polygon :
3178 op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
3179 break;
3180 case xd_filled_bezier :
3181 case xd_unfilled_bezier :
3182 op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
3183 break;
3184 case xd_polyline :
3185 op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
3186 break;
3187 case xd_text :
3188 op->span = gv_alloc(sizeof(textspan_t));
3189 op->span->str = gv_strdup (op->op.u.text.text);
3190 op->span->just = adjust [op->op.u.text.align];
3191 tf.name = fontname;
3192 tf.size = fontsize;
3193 tf.flags = fontflags;
3194 op->span->font = dtinsert(gvc->textfont_dt, &tf);
3195 textspan_size (gvc, op->span);
3196 bb0 = textBB (op->op.u.text.x, op->op.u.text.y, op->span);
3197 op->bb = bb0;
3198 expandBB (&bb, bb0.LL);
3199 expandBB (&bb, bb0.UR);
3200 if (!xd->freefunc)
3201 xd->freefunc = freePara;
3202 break;
3203 case xd_font :
3204 fontsize = op->op.u.font.size;
3205 fontname = op->op.u.font.name;
3206 break;
3207 case xd_fontchar :
3208 fontflags = op->op.u.fontchar;
3209 break;
3210 default :
3211 break;
3212 }
3213 op++;
3214 }
3215 return bb;
3216}
3217
3218static void init_gvc(GVC_t * gvc, graph_t * g)
3219{
3220 double xf, yf;
3221 char *p;
3222 int i;
3223
3224 gvc->g = g;
3225
3226 /* margins */
3227 gvc->graph_sets_margin = false;
3228 if ((p = agget(g, "margin"))) {
3229 i = sscanf(p, "%lf,%lf", &xf, &yf);
3230 if (i > 0) {
3231 gvc->margin.x = gvc->margin.y = xf * POINTS_PER_INCH;
3232 if (i > 1)
3233 gvc->margin.y = yf * POINTS_PER_INCH;
3234 gvc->graph_sets_margin = true;
3235 }
3236 }
3237
3238 /* pad */
3239 gvc->graph_sets_pad = false;
3240 if ((p = agget(g, "pad"))) {
3241 i = sscanf(p, "%lf,%lf", &xf, &yf);
3242 if (i > 0) {
3243 gvc->pad.x = gvc->pad.y = xf * POINTS_PER_INCH;
3244 if (i > 1)
3245 gvc->pad.y = yf * POINTS_PER_INCH;
3246 gvc->graph_sets_pad = true;
3247 }
3248 }
3249
3250 /* pagesize */
3251 gvc->graph_sets_pageSize = false;
3252 gvc->pageSize = GD_drawing(g)->page;
3253 if (GD_drawing(g)->page.x > 0.001 && GD_drawing(g)->page.y > 0.001)
3254 gvc->graph_sets_pageSize = true;
3255
3256 /* rotation */
3257 if (GD_drawing(g)->landscape)
3258 gvc->rotation = 90;
3259 else
3260 gvc->rotation = 0;
3261
3262 /* pagedir */
3263 gvc->pagedir = "BL";
3264 if ((p = agget(g, "pagedir")) && p[0])
3265 gvc->pagedir = p;
3266
3267
3268 /* bounding box */
3269 gvc->bb = GD_bb(g);
3270
3271 /* clusters have peripheries */
3272 G_peripheries = agfindgraphattr(g, "peripheries");
3273 G_penwidth = agfindgraphattr(g, "penwidth");
3274
3275 /* default font */
3280
3281 /* default line style */
3283
3284 gvc->graphname = agnameof(g);
3285}
3286
3287static void init_job_pad(GVJ_t *job)
3288{
3289 GVC_t *gvc = job->gvc;
3290
3291 if (gvc->graph_sets_pad) {
3292 job->pad = gvc->pad;
3293 }
3294 else {
3295 switch (job->output_lang) {
3296 case GVRENDER_PLUGIN:
3297 job->pad.x = job->pad.y = job->render.features->default_pad;
3298 break;
3299 default:
3300 job->pad.x = job->pad.y = DEFAULT_GRAPH_PAD;
3301 break;
3302 }
3303 }
3304}
3305
3306static void init_job_margin(GVJ_t *job)
3307{
3308 GVC_t *gvc = job->gvc;
3309
3310 if (gvc->graph_sets_margin) {
3311 job->margin = gvc->margin;
3312 }
3313 else {
3314 /* set default margins depending on format */
3315 switch (job->output_lang) {
3316 case GVRENDER_PLUGIN:
3317 job->margin = job->device.features->default_margin;
3318 break;
3319 case PCL: case MIF: case METAPOST: case VTX: case QPDF:
3320 job->margin.x = job->margin.y = DEFAULT_PRINT_MARGIN;
3321 break;
3322 default:
3323 job->margin.x = job->margin.y = DEFAULT_EMBED_MARGIN;
3324 break;
3325 }
3326 }
3327
3328}
3329
3330static void init_job_dpi(GVJ_t *job, graph_t *g)
3331{
3332 GVJ_t *firstjob = job->gvc->active_jobs;
3333
3334 if (GD_drawing(g)->dpi > 0) {
3335 job->dpi.x = job->dpi.y = GD_drawing(g)->dpi;
3336 }
3337 else if (firstjob && firstjob->device_sets_dpi) {
3338 job->dpi = firstjob->device_dpi; /* some devices set dpi in initialize() */
3339 }
3340 else {
3341 /* set default margins depending on format */
3342 switch (job->output_lang) {
3343 case GVRENDER_PLUGIN:
3344 job->dpi = job->device.features->default_dpi;
3345 break;
3346 default:
3347 job->dpi.x = job->dpi.y = DEFAULT_DPI;
3348 break;
3349 }
3350 }
3351}
3352
3353static void init_job_viewport(GVJ_t * job, graph_t * g)
3354{
3355 GVC_t *gvc = job->gvc;
3356 pointf LL, UR, size, sz;
3357 double Z;
3358 int rv;
3359 Agnode_t *n;
3360 char *str, *nodename = NULL;
3361
3362 UR = gvc->bb.UR;
3363 LL = gvc->bb.LL;
3364 job->bb.LL = sub_pointf(LL, job->pad); // job->bb is bb of graph and padding - graph units
3365 job->bb.UR = add_pointf(UR, job->pad);
3366 sz = sub_pointf(job->bb.UR, job->bb.LL); // size, including padding - graph units
3367
3368 /* determine final drawing size and scale to apply. */
3369 /* N.B. size given by user is not rotated by landscape mode */
3370 /* start with "natural" size of layout */
3371
3372 Z = 1.0;
3373 if (GD_drawing(g)->size.x > 0.001 && GD_drawing(g)->size.y > 0.001) { /* graph size was given by user... */
3374 size = GD_drawing(g)->size;
3375 if (sz.x <= 0.001) sz.x = size.x;
3376 if (sz.y <= 0.001) sz.y = size.y;
3377 if (size.x < sz.x || size.y < sz.y /* drawing is too big (in either axis) ... */
3378 || (GD_drawing(g)->filled /* or ratio=filled requested and ... */
3379 && size.x > sz.x && size.y > sz.y)) /* drawing is too small (in both axes) ... */
3380 Z = fmin(size.x / sz.x, size.y / sz.y);
3381 }
3382
3383 /* default focus, in graph units = center of bb */
3384 pointf xy = scale(0.5, add_pointf(LL, UR));
3385
3386 /* rotate and scale bb to give default absolute size in points*/
3387 job->rotation = job->gvc->rotation;
3388 pointf XY = scale(Z, sz);
3389
3390 /* user can override */
3391 if ((str = agget(g, "viewport"))) {
3392 nodename = gv_alloc(strlen(str) + 1);
3393 rv = sscanf(str, "%lf,%lf,%lf,\'%[^\']\'", &XY.x, &XY.y, &Z, nodename);
3394 if (rv == 4) {
3395 n = agfindnode(g->root, nodename);
3396 if (n) {
3397 xy = ND_coord(n);
3398 }
3399 }
3400 else {
3401 rv = sscanf(str, "%lf,%lf,%lf,%[^,]%c", &XY.x, &XY.y, &Z, nodename,
3402 &(char){0});
3403 if (rv == 4) {
3404 n = agfindnode(g->root, nodename);
3405 if (n) {
3406 xy = ND_coord(n);
3407 }
3408 }
3409 else {
3410 sscanf(str, "%lf,%lf,%lf,%lf,%lf", &XY.x, &XY.y, &Z, &xy.x, &xy.y);
3411 }
3412 }
3413 free (nodename);
3414 }
3415 /* rv is ignored since args retain previous values if not scanned */
3416
3417 /* job->view gives port size in graph units, unscaled or rotated
3418 * job->zoom gives scaling factor.
3419 * job->focus gives the position in the graph of the center of the port
3420 */
3421 job->view = XY;
3422 job->zoom = Z; /* scaling factor */
3423 job->focus = xy;
3424}
3425
3426static void emit_cluster_colors(GVJ_t * job, graph_t * g)
3427{
3428 graph_t *sg;
3429 int c;
3430 char *str;
3431
3432 for (c = 1; c <= GD_n_cluster(g); c++) {
3433 sg = GD_clust(g)[c];
3434 emit_cluster_colors(job, sg);
3435 if (((str = agget(sg, "color")) != 0) && str[0])
3437 if (((str = agget(sg, "pencolor")) != 0) && str[0])
3439 if (((str = agget(sg, "bgcolor")) != 0) && str[0])
3441 if (((str = agget(sg, "fillcolor")) != 0) && str[0])
3443 if (((str = agget(sg, "fontcolor")) != 0) && str[0])
3445 }
3446}
3447
3448static void emit_colors(GVJ_t * job, graph_t * g)
3449{
3450 node_t *n;
3451 edge_t *e;
3452 char *str, *colors;
3453
3455 if (((str = agget(g, "bgcolor")) != 0) && str[0])
3457 if (((str = agget(g, "fontcolor")) != 0) && str[0])
3459
3460 emit_cluster_colors(job, g);
3461 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3462 if (((str = agget(n, "color")) != 0) && str[0])
3464 if (((str = agget(n, "pencolor")) != 0) && str[0])
3466 if (((str = agget(n, "fillcolor")) != 0) && str[0]) {
3467 if (strchr(str, ':')) {
3468 colors = gv_strdup(str);
3469 for (str = strtok(colors, ":"); str;
3470 str = strtok(0, ":")) {
3471 if (str[0])
3473 }
3474 free(colors);
3475 }
3476 else {
3478 }
3479 }
3480 if (((str = agget(n, "fontcolor")) != 0) && str[0])
3482 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3483 if (((str = agget(e, "color")) != 0) && str[0]) {
3484 if (strchr(str, ':')) {
3485 colors = gv_strdup(str);
3486 for (str = strtok(colors, ":"); str;
3487 str = strtok(0, ":")) {
3488 if (str[0])
3490 }
3491 free(colors);
3492 }
3493 else {
3495 }
3496 }
3497 if (((str = agget(e, "fontcolor")) != 0) && str[0])
3499 }
3500 }
3501}
3502
3503static void emit_view(GVJ_t * job, graph_t * g, int flags)
3504{
3505 GVC_t * gvc = job->gvc;
3506 node_t *n;
3507 edge_t *e;
3508
3509 gvc->common.viewNum++;
3510 /* when drawing, lay clusters down before nodes and edges */
3511 if (!(flags & EMIT_CLUSTERS_LAST))
3512 emit_clusters(job, g, flags);
3513 if (flags & EMIT_SORTED) {
3514 /* output all nodes, then all edges */
3516 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3517 emit_node(job, n);
3518 gvrender_end_nodes(job);
3520 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3521 for (e = agfstout(g, n); e; e = agnxtout(g, e))
3522 emit_edge(job, e);
3523 }
3524 gvrender_end_edges(job);
3525 } else if (flags & EMIT_EDGE_SORTED) {
3526 /* output all edges, then all nodes */
3528 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3529 for (e = agfstout(g, n); e; e = agnxtout(g, e))
3530 emit_edge(job, e);
3531 gvrender_end_edges(job);
3533 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3534 emit_node(job, n);
3535 gvrender_end_nodes(job);
3536 } else if (flags & EMIT_PREORDER) {
3538 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3539 if (write_node_test(g, n))
3540 emit_node(job, n);
3541 gvrender_end_nodes(job);
3543
3544 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3545 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3546 if (write_edge_test(g, e))
3547 emit_edge(job, e);
3548 }
3549 }
3550 gvrender_end_edges(job);
3551 } else {
3552 /* output in breadth first graph walk order */
3553 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
3554 emit_node(job, n);
3555 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
3556 emit_node(job, aghead(e));
3557 emit_edge(job, e);
3558 }
3559 }
3560 }
3561 /* when mapping, detect events on clusters after nodes and edges */
3563 emit_clusters(job, g, flags);
3564}
3565
3566static void emit_begin_graph(GVJ_t * job, graph_t * g)
3567{
3568 obj_state_t *obj;
3569
3570 obj = push_obj_state(job);
3571 obj->type = ROOTGRAPH_OBJTYPE;
3572 obj->u.g = g;
3573 obj->emit_state = EMIT_GDRAW;
3574
3575 initObjMapData (job, GD_label(g), g);
3576
3578}
3579
3580static void emit_end_graph(GVJ_t * job)
3581{
3582 gvrender_end_graph(job);
3583 pop_obj_state(job);
3584}
3585
3586#define NotFirstPage(j) (((j)->layerNum>1)||((j)->pagesArrayElem.x > 0)||((j)->pagesArrayElem.x > 0))
3587
3588static void emit_page(GVJ_t * job, graph_t * g)
3589{
3590 obj_state_t *obj = job->obj;
3591 int flags = job->flags;
3592 size_t nump = 0;
3593 textlabel_t *lab;
3594 pointf *p = NULL;
3595 char* saveid;
3596 agxbuf xb = {0};
3597
3598 /* For the first page, we can use the values generated in emit_begin_graph.
3599 * For multiple pages, we need to generate a new id.
3600 */
3601 bool obj_id_needs_restore = false;
3602 if (NotFirstPage(job)) {
3603 saveid = obj->id;
3604 layerPagePrefix (job, &xb);
3605 agxbput(&xb, saveid == NULL ? "layer" : saveid);
3606 obj->id = agxbuse(&xb);
3607 obj_id_needs_restore = true;
3608 }
3609 else
3610 saveid = NULL;
3611
3612 char *previous_color_scheme = setColorScheme(agget(g, "colorscheme"));
3613 setup_page(job);
3618 && (obj->url || obj->explicit_tooltip)) {
3622 nump = 2;
3623 }
3624 else {
3626 nump = 4;
3627 }
3628 p = gv_calloc(nump, sizeof(pointf));
3629 p[0] = job->pageBox.LL;
3630 p[1] = job->pageBox.UR;
3632 rect2poly(p);
3633 }
3635 gvrender_ptf_A(job, p, p, nump);
3636 obj->url_map_p = p;
3637 obj->url_map_n = nump;
3638 }
3639 if ((flags & GVRENDER_DOES_LABELS) && ((lab = GD_label(g))))
3640 /* do graph label on every page and rely on clipping to show it on the right one(s) */
3641 obj->label = lab->text;
3642 /* If EMIT_CLUSTERS_LAST is set, we assume any URL or tooltip
3643 * attached to the root graph is emitted either in begin_page
3644 * or end_page of renderer.
3645 */
3646 if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip)) {
3647 emit_map_rect(job, job->clip);
3648 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3649 }
3650 emit_background(job, g);
3651 if (GD_label(g))
3653 if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip))
3655 emit_view(job,g,flags);
3656 gvrender_end_page(job);
3657 if (obj_id_needs_restore) {
3658 obj->id = saveid;
3659 }
3660 agxbfree(&xb);
3661
3662 char *color_scheme = setColorScheme(previous_color_scheme);
3663 free(color_scheme);
3664 free(previous_color_scheme);
3665}
3666
3667void emit_graph(GVJ_t * job, graph_t * g)
3668{
3669 node_t *n;
3670 char *s;
3671 int flags = job->flags;
3672 int* lp;
3673
3674 /* device dpi is now known */
3675 job->scale.x = job->zoom * job->dpi.x / POINTS_PER_INCH;
3676 job->scale.y = job->zoom * job->dpi.y / POINTS_PER_INCH;
3677
3678 job->devscale.x = job->dpi.x / POINTS_PER_INCH;
3679 job->devscale.y = job->dpi.y / POINTS_PER_INCH;
3680 if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
3681 job->devscale.y *= -1;
3682
3683 /* compute current view in graph units */
3684 if (job->rotation) {
3685 job->view.y = job->width / job->scale.y;
3686 job->view.x = job->height / job->scale.x;
3687 }
3688 else {
3689 job->view.x = job->width / job->scale.x;
3690 job->view.y = job->height / job->scale.y;
3691 }
3692
3693 s = late_string(g, agattr_text(g, AGRAPH, "comment", 0), "");
3694 gvrender_comment(job, s);
3695
3696 job->layerNum = 0;
3697 emit_begin_graph(job, g);
3698
3699 if (flags & EMIT_COLORS)
3700 emit_colors(job,g);
3701
3702 /* reset node state */
3703 for (n = agfstnode(g); n; n = agnxtnode(g, n))
3704 ND_state(n) = 0;
3705 /* iterate layers */
3706 for (firstlayer(job,&lp); validlayer(job); nextlayer(job,&lp)) {
3707 if (numPhysicalLayers (job) > 1)
3709
3710 /* iterate pages */
3711 for (firstpage(job); validpage(job); nextpage(job))
3712 emit_page(job, g);
3713
3714 if (numPhysicalLayers (job) > 1)
3715 gvrender_end_layer(job);
3716 }
3717 emit_end_graph(job);
3718}
3719
3722 .link = -1, // link - allocate separate holder objects
3723 .freef = free,
3724};
3725
3726bool emit_once(char *str) {
3727 // use stderr as a mutual exclusion mechanism for `strings`
3728 lockfile(stderr);
3729
3730 if (strings == 0)
3732 if (!dtsearch(strings, str)) {
3734 unlockfile(stderr);
3735 return true;
3736 }
3737 unlockfile(stderr);
3738 return false;
3739}
3740
3742{
3743 // use stderr as a mutual exclusion mechanism for `strings`
3744 lockfile(stderr);
3745
3746 if (strings) {
3748 strings = 0;
3749 }
3750 unlockfile(stderr);
3751}
3752
3753static void emit_begin_cluster(GVJ_t * job, Agraph_t * sg)
3754{
3755 obj_state_t *obj;
3756
3757 obj = push_obj_state(job);
3758 obj->type = CLUSTER_OBJTYPE;
3759 obj->u.sg = sg;
3760 obj->emit_state = EMIT_CDRAW;
3761
3762 initObjMapData (job, GD_label(sg), sg);
3763
3765}
3766
3767static void emit_end_cluster(GVJ_t *job) {
3769 pop_obj_state(job);
3770}
3771
3772void emit_clusters(GVJ_t * job, Agraph_t * g, int flags)
3773{
3774 int doPerim, c, filled;
3775 pointf AF[4];
3776 char *color, *fillcolor, *pencolor, **style, *s;
3777 graph_t *sg;
3778 node_t *n;
3779 edge_t *e;
3780 obj_state_t *obj;
3781 textlabel_t *lab;
3782 int doAnchor;
3783 double penwidth;
3784
3785 for (c = 1; c <= GD_n_cluster(g); c++) {
3786 sg = GD_clust(g)[c];
3787 if (!clust_in_layer(job, sg))
3788 continue;
3789 /* when mapping, detect events on clusters after sub_clusters */
3791 emit_clusters(job, sg, flags);
3792 emit_begin_cluster(job, sg);
3793 obj = job->obj;
3794 doAnchor = obj->url || obj->explicit_tooltip;
3795 char *previous_color_scheme = setColorScheme(agget(sg, "colorscheme"));
3796 if (doAnchor && !(flags & EMIT_CLUSTERS_LAST)) {
3797 emit_map_rect(job, GD_bb(sg));
3798 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3799 }
3800 filled = 0;
3801 graphviz_polygon_style_t istyle = {0};
3802 if ((style = checkClusterStyle(sg, &istyle))) {
3803 gvrender_set_style(job, style);
3804 if (istyle.filled)
3805 filled = FILL;
3806 }
3807 fillcolor = pencolor = 0;
3808
3809 if (GD_gui_state(sg) & GUI_STATE_ACTIVE) {
3810 pencolor = DEFAULT_ACTIVEPENCOLOR;
3811 fillcolor = DEFAULT_ACTIVEFILLCOLOR;
3812 filled = FILL;
3813 }
3814 else if (GD_gui_state(sg) & GUI_STATE_SELECTED) {
3815 pencolor = DEFAULT_SELECTEDPENCOLOR;
3816 fillcolor = DEFAULT_SELECTEDFILLCOLOR;
3817 filled = FILL;
3818 }
3819 else if (GD_gui_state(sg) & GUI_STATE_DELETED) {
3820 pencolor = DEFAULT_DELETEDPENCOLOR;
3821 fillcolor = DEFAULT_DELETEDFILLCOLOR;
3822 filled = FILL;
3823 }
3824 else if (GD_gui_state(sg) & GUI_STATE_VISITED) {
3825 pencolor = DEFAULT_VISITEDPENCOLOR;
3826 fillcolor = DEFAULT_VISITEDFILLCOLOR;
3827 filled = FILL;
3828 }
3829 else {
3830 if ((color = agget(sg, "color")) != 0 && color[0])
3831 fillcolor = pencolor = color;
3832 if ((color = agget(sg, "pencolor")) != 0 && color[0])
3833 pencolor = color;
3834 if ((color = agget(sg, "fillcolor")) != 0 && color[0])
3835 fillcolor = color;
3836 /* bgcolor is supported for backward compatibility
3837 if fill is set, fillcolor trumps bgcolor, so
3838 don't bother checking.
3839 if gradient is set fillcolor trumps bgcolor
3840 */
3841 if ((filled == 0 || !fillcolor) && (color = agget(sg, "bgcolor")) != 0 && color[0]) {
3842 fillcolor = color;
3843 filled = FILL;
3844 }
3845
3846 }
3847 if (!pencolor) pencolor = DEFAULT_COLOR;
3848 if (!fillcolor) fillcolor = DEFAULT_FILL;
3849 char *clrs[2] = {0};
3850 if (filled != 0) {
3851 double frac;
3852 if (findStopColor (fillcolor, clrs, &frac)) {
3853 gvrender_set_fillcolor(job, clrs[0]);
3854 if (clrs[1])
3855 gvrender_set_gradient_vals(job,clrs[1],late_int(sg,G_gradientangle,0,0), frac);
3856 else
3858 if (istyle.radial)
3859 filled = RGRADIENT;
3860 else
3861 filled = GRADIENT;
3862 }
3863 else
3864 gvrender_set_fillcolor(job, fillcolor);
3865 }
3866
3867 if (G_penwidth && ((s = agxget(sg, G_penwidth)) && s[0])) {
3868 penwidth = late_double(sg, G_penwidth, 1.0, 0.0);
3870 }
3871
3872 if (istyle.rounded) {
3873 if ((doPerim = late_int(sg, G_peripheries, 1, 0)) || filled != 0) {
3874 AF[0] = GD_bb(sg).LL;
3875 AF[2] = GD_bb(sg).UR;
3876 AF[1].x = AF[2].x;
3877 AF[1].y = AF[0].y;
3878 AF[3].x = AF[0].x;
3879 AF[3].y = AF[2].y;
3880 if (doPerim)
3881 gvrender_set_pencolor(job, pencolor);
3882 else
3883 gvrender_set_pencolor(job, "transparent");
3884 round_corners(job, AF, 4, istyle, filled);
3885 }
3886 }
3887 else if (istyle.striped) {
3888 AF[0] = GD_bb(sg).LL;
3889 AF[2] = GD_bb(sg).UR;
3890 AF[1].x = AF[2].x;
3891 AF[1].y = AF[0].y;
3892 AF[3].x = AF[0].x;
3893 AF[3].y = AF[2].y;
3894 if (late_int(sg, G_peripheries, 1, 0) == 0)
3895 gvrender_set_pencolor(job, "transparent");
3896 else
3897 gvrender_set_pencolor(job, pencolor);
3898 if (stripedBox (job, AF, fillcolor, 0) > 1)
3899 agerr (AGPREV, "in cluster %s\n", agnameof(sg));
3900 gvrender_box(job, GD_bb(sg), 0);
3901 }
3902 else {
3903 if (late_int(sg, G_peripheries, 1, 0)) {
3904 gvrender_set_pencolor(job, pencolor);
3905 gvrender_box(job, GD_bb(sg), filled);
3906 }
3907 else if (filled != 0) {
3908 gvrender_set_pencolor(job, "transparent");
3909 gvrender_box(job, GD_bb(sg), filled);
3910 }
3911 }
3912
3913 free (clrs[0]);
3914 free(clrs[1]);
3915 if ((lab = GD_label(sg)))
3916 emit_label(job, EMIT_CLABEL, lab);
3917
3918 if (doAnchor) {
3919 if (flags & EMIT_CLUSTERS_LAST) {
3920 emit_map_rect(job, GD_bb(sg));
3921 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
3922 }
3924 }
3925
3926 if (flags & EMIT_PREORDER) {
3927 for (n = agfstnode(sg); n; n = agnxtnode(sg, n)) {
3928 emit_node(job, n);
3929 for (e = agfstout(sg, n); e; e = agnxtout(sg, e))
3930 emit_edge(job, e);
3931 }
3932 }
3933 emit_end_cluster(job);
3934 /* when drawing, lay down clusters before sub_clusters */
3935 if (!(flags & EMIT_CLUSTERS_LAST))
3936 emit_clusters(job, sg, flags);
3937
3938 char *color_scheme = setColorScheme(previous_color_scheme);
3939 free(color_scheme);
3940 free(previous_color_scheme);
3941 }
3942}
3943
3944static bool is_style_delim(int c)
3945{
3946 switch (c) {
3947 case '(':
3948 case ')':
3949 case ',':
3950 case '\0':
3951 return true;
3952 default:
3953 return false;
3954 }
3955}
3956
3957#define SID 1
3958
3963typedef struct {
3964 int type;
3965 const char *start;
3966 size_t size;
3967} token_t;
3968
3969static token_t style_token(char **s) {
3970 char *p = *s;
3971 int token;
3972
3973 while (gv_isspace(*p) || *p == ',')
3974 p++;
3975 const char *start = p;
3976 switch (*p) {
3977 case '\0':
3978 token = 0;
3979 break;
3980 case '(':
3981 case ')':
3982 token = *p++;
3983 break;
3984 default:
3985 token = SID;
3986 while (!is_style_delim(*p)) {
3987 p++;
3988 }
3989 }
3990 *s = p;
3991 assert(start <= p);
3992 size_t size = (size_t)(p - start);
3993 return (token_t){.type = token, .start = start, .size = size};
3994}
3995
3996#define FUNLIMIT 64
3997
3998/* This is one of the worst internal designs in graphviz.
3999 * The use of '\0' characters within strings seems cute but it
4000 * makes all of the standard functions useless if not dangerous.
4001 * Plus the function uses static memory for both the array and
4002 * the character buffer. One hopes all of the values are used
4003 * before the function is called again.
4004 */
4005char **parse_style(char *s)
4006{
4007 static char *parse[FUNLIMIT];
4008 size_t parse_offsets[sizeof(parse) / sizeof(parse[0])];
4009 size_t fun = 0;
4010 bool in_parens = false;
4011 char *p;
4012 static agxbuf ps_xb;
4013
4014 p = s;
4015 while (true) {
4016 token_t c = style_token(&p);
4017 if (c.type == 0) {
4018 break;
4019 }
4020 switch (c.type) {
4021 case '(':
4022 if (in_parens) {
4023 agerrorf("nesting not allowed in style: %s\n", s);
4024 parse[0] = NULL;
4025 return parse;
4026 }
4027 in_parens = true;
4028 break;
4029
4030 case ')':
4031 if (!in_parens) {
4032 agerrorf("unmatched ')' in style: %s\n", s);
4033 parse[0] = NULL;
4034 return parse;
4035 }
4036 in_parens = false;
4037 break;
4038
4039 default:
4040 if (!in_parens) {
4041 if (fun == FUNLIMIT - 1) {
4042 agwarningf("truncating style '%s'\n", s);
4043 parse[fun] = NULL;
4044 return parse;
4045 }
4046 agxbputc(&ps_xb, '\0'); /* terminate previous */
4047 parse_offsets[fun++] = agxblen(&ps_xb);
4048 }
4049 agxbput_n(&ps_xb, c.start, c.size);
4050 agxbputc(&ps_xb, '\0');
4051 }
4052 }
4053
4054 if (in_parens) {
4055 agerrorf("unmatched '(' in style: %s\n", s);
4056 parse[0] = NULL;
4057 return parse;
4058 }
4059
4060 char *base = agxbuse(&ps_xb); // add final '\0' to buffer
4061
4062 // construct list of style strings
4063 for (size_t i = 0; i < fun; ++i) {
4064 parse[i] = base + parse_offsets[i];
4065 }
4066 parse[fun] = NULL;
4067
4068 return parse;
4069}
4070
4072{
4073 pointf p, p1, p2;
4074 boxf bb;
4075
4076 assert(bz.size > 0);
4077 assert(bz.size % 3 == 1);
4078 bb.LL = bb.UR = bz.list[0];
4079 for (size_t i = 1; i < bz.size;) {
4080 /* take mid-point between two control points for bb calculation */
4081 p1=bz.list[i];
4082 i++;
4083 p2=bz.list[i];
4084 i++;
4085 p.x = ( p1.x + p2.x ) / 2;
4086 p.y = ( p1.y + p2.y ) / 2;
4087 expandbp(&bb, p);
4088
4089 p=bz.list[i];
4090 expandbp(&bb, p);
4091 i++;
4092 }
4093 return bb;
4094}
4095
4096static void init_splines_bb(splines *spl)
4097{
4098 bezier bz;
4099 boxf bb, b;
4100
4101 assert(spl->size > 0);
4102 bz = spl->list[0];
4103 bb = bezier_bb(bz);
4104 for (size_t i = 0; i < spl->size; i++) {
4105 if (i > 0) {
4106 bz = spl->list[i];
4107 b = bezier_bb(bz);
4108 EXPANDBB(&bb, b);
4109 }
4110 if (bz.sflag) {
4111 b = arrow_bb(bz.sp, bz.list[0], 1);
4112 EXPANDBB(&bb, b);
4113 }
4114 if (bz.eflag) {
4115 b = arrow_bb(bz.ep, bz.list[bz.size - 1], 1);
4116 EXPANDBB(&bb, b);
4117 }
4118 }
4119 spl->bb = bb;
4120}
4121
4122static void init_bb_edge(edge_t *e)
4123{
4124 splines *spl;
4125
4126 spl = ED_spl(e);
4127 if (spl)
4128 init_splines_bb(spl);
4129}
4130
4131static void init_bb_node(graph_t *g, node_t *n)
4132{
4133 edge_t *e;
4134
4135 ND_bb(n).LL.x = ND_coord(n).x - ND_lw(n);
4136 ND_bb(n).LL.y = ND_coord(n).y - ND_ht(n) / 2.;
4137 ND_bb(n).UR.x = ND_coord(n).x + ND_rw(n);
4138 ND_bb(n).UR.y = ND_coord(n).y + ND_ht(n) / 2.;
4139
4140 for (e = agfstout(g, n); e; e = agnxtout(g, e))
4141 init_bb_edge(e);
4142
4143 /* IDEA - could also save in the node the bb of the node and
4144 all of its outedges, then the scan time would be proportional
4145 to just the number of nodes for many graphs.
4146 Wouldn't work so well if the edges are sprawling all over the place
4147 because then the boxes would overlap a lot and require more tests,
4148 but perhaps that wouldn't add much to the cost before trying individual
4149 nodes and edges. */
4150}
4151
4152static void init_bb(graph_t *g)
4153{
4154 node_t *n;
4155
4156 for (n = agfstnode(g); n; n = agnxtnode(g, n))
4157 init_bb_node(g, n);
4158}
4159
4161extern const size_t gvevent_key_binding_size;
4163
4164/* Set LC_NUMERIC to "C" to get expected interpretation of %f
4165 * in printf functions. Languages like postscript and dot expect
4166 * floating point numbers to use a decimal point.
4167 *
4168 * If set is non-zero, the "C" locale set;
4169 * if set is zero, the original locale is reset.
4170 * Calls to the function can nest.
4171 */
4172void gv_fixLocale (int set)
4173{
4174 static char* save_locale;
4175 static int cnt;
4176
4177 if (set) {
4178 cnt++;
4179 if (cnt == 1) {
4180 save_locale = gv_strdup(setlocale (LC_NUMERIC, NULL));
4181 setlocale (LC_NUMERIC, "C");
4182 }
4183 }
4184 else if (cnt > 0) {
4185 cnt--;
4186 if (cnt == 0) {
4187 setlocale (LC_NUMERIC, save_locale);
4188 free (save_locale);
4189 }
4190 }
4191}
4192
4193
4194#define FINISH() \
4195 GV_DEBUG("gvRenderJobs %s: %.2f secs.", agnameof(g), elapsed_sec())
4196
4198{
4199 static GVJ_t *prevjob;
4200 GVJ_t *job, *firstjob;
4201
4202 if (Verbose)
4203 start_timer();
4204
4205 if (!LAYOUT_DONE(g)) {
4206 agerrorf("Layout was not done. Missing layout plugins? \n");
4207 FINISH();
4208 return -1;
4209 }
4210
4211 init_bb(g);
4212 init_gvc(gvc, g);
4213 init_layering(gvc, g);
4214
4215 gv_fixLocale (1);
4216 for (job = gvjobs_first(gvc); job; job = gvjobs_next(gvc)) {
4217 if (gvc->gvg) {
4219 job->graph_index = gvc->gvg->graph_index;
4220 }
4221 else {
4222 job->input_filename = NULL;
4223 job->graph_index = 0;
4224 }
4225 job->common = &gvc->common;
4226 job->layout_type = gvc->layout.type;
4229 if (!GD_drawing(g)) {
4230 agerrorf("layout was not done\n");
4231 gv_fixLocale (0);
4232 FINISH();
4233 return -1;
4234 }
4235
4237 if (job->output_lang == NO_SUPPORT) {
4238 agerrorf("renderer for %s is unavailable\n", job->output_langname);
4239 gv_fixLocale (0);
4240 FINISH();
4241 return -1;
4242 }
4243
4244 switch (job->output_lang) {
4245 case VTX:
4246 /* output sorted, i.e. all nodes then all edges */
4247 job->flags |= EMIT_SORTED;
4248 break;
4249 default:
4250 job->flags |= chkOrder(g);
4251 break;
4252 }
4253
4254 // if we already have an active job list and the device doesn't support
4255 // multiple output files, or we are about to write to a different output
4256 // device
4257 firstjob = gvc->active_jobs;
4258 if (firstjob) {
4259 if (! (firstjob->flags & GVDEVICE_DOES_PAGES)
4260 || strcmp(job->output_langname, firstjob->output_langname)) {
4261
4262 gvrender_end_job(firstjob);
4263
4264 gvc->active_jobs = NULL; /* clear active list */
4265 gvc->common.viewNum = 0;
4266 prevjob = NULL;
4267 }
4268 }
4269 else {
4270 prevjob = NULL;
4271 }
4272
4273 if (prevjob) {
4274 prevjob->next_active = job; /* insert job in active list */
4275 job->output_file = prevjob->output_file; /* FIXME - this is dumb ! */
4276 }
4277 else {
4278 if (gvrender_begin_job(job))
4279 continue;
4280 gvc->active_jobs = job; /* first job of new list */
4281 }
4282 job->next_active = NULL; /* terminate active list */
4284
4285 init_job_pad(job);
4286 init_job_margin(job);
4287 init_job_dpi(job, g);
4288 init_job_viewport(job, g);
4289 init_job_pagination(job, g);
4290
4291 if (! (job->flags & GVDEVICE_EVENTS)) {
4292 if (debug) {
4293 // Show_boxes is not defined, if at all, until splines are generated in dot
4296// FIXME: remove the cast and change `show_boxes` to a `char **` at the next API
4297// break
4298#ifdef __GNUC__
4299#pragma GCC diagnostic push
4300#pragma GCC diagnostic ignored "-Wcast-qual"
4301#endif
4302 job->common->show_boxes = (const char **)LIST_FRONT(&Show_boxes);
4303#ifdef __GNUC__
4304#pragma GCC diagnostic pop
4305#endif
4306 }
4307 emit_graph(job, g);
4308 }
4309
4310 /* the last job, after all input graphs are processed,
4311 * is finalized from gvFinalize()
4312 */
4313 prevjob = job;
4314 }
4315 gv_fixLocale (0);
4316 FINISH();
4317 return 0;
4318}
4319
4320/* Check for colon in colorlist. If one exists, and not the first
4321 * character, store the characters before the colon in clrs[0] and
4322 * the characters after the colon (and before the next or end-of-string)
4323 * in clrs[1]. If there are no characters after the first colon, clrs[1]
4324 * is NULL. Return TRUE.
4325 * If there is no non-trivial string before a first colon, set clrs[0] to
4326 * NULL and return FALSE.
4327 *
4328 * Note that memory for clrs must be freed by calling function.
4329 */
4330bool findStopColor(const char *colorlist, char *clrs[2], double *frac) {
4331 colorsegs_t segs = {.dtor = freeSeg};
4332 int rv;
4333 clrs[0] = NULL;
4334 clrs[1] = NULL;
4335
4336 rv = parseSegs(colorlist, &segs);
4337 if (rv || LIST_SIZE(&segs) < 2 || LIST_FRONT(&segs)->color == NULL) {
4338 LIST_FREE(&segs);
4339 return false;
4340 }
4341
4342 if (LIST_SIZE(&segs) > 2)
4343 agwarningf("More than 2 colors specified for a gradient - ignoring remaining\n");
4344
4345 clrs[0] = gv_strdup(LIST_FRONT(&segs)->color);
4346 if (LIST_GET(&segs, 1).color) {
4347 clrs[1] = gv_strdup(LIST_GET(&segs, 1).color);
4348 }
4349
4350 if (LIST_FRONT(&segs)->hasFraction)
4351 *frac = LIST_FRONT(&segs)->t;
4352 else if (LIST_GET(&segs, 1).hasFraction)
4353 *frac = 1 - LIST_GET(&segs, 1).t;
4354 else
4355 *frac = 0;
4356
4357 LIST_FREE(&segs);
4358 return true;
4359}
4360
static agxbuf last
last message
Definition agerror.c:31
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:268
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
static size_t agxblen(const agxbuf *xb)
return number of characters currently stored
Definition agxbuf.h:108
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:295
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
#define BETWEEN(a, b, c)
Definition arith.h:38
#define ROUND(f)
Definition arith.h:48
#define M_PI
Definition arith.h:41
boxf arrow_bb(pointf p, pointf u, double arrowsize)
Definition arrows.c:1113
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:1151
container data types API
#define dtsearch(d, o)
Definition cdt.h:184
#define dtinsert(d, o)
Definition cdt.h:186
CDT_API int dtclose(Dt_t *)
Definition dtclose.c:10
CDT_API Dtmethod_t * Dtoset
ordered set (self-adjusting tree)
Definition dttree.c:306
CDT_API Dt_t * dtopen(Dtdisc_t *, Dtmethod_t *)
Definition dtopen.c:11
#define parent(i)
Definition closest.c:75
#define right(i)
Definition closest.c:74
COLORPROCS_API char * setColorScheme(const char *s)
Definition colxlate.c:397
char * late_nnstring(void *obj, attrsym_t *attr, char *defaultValue)
Definition utils.c:90
bool mapbool(const char *p)
Definition utils.c:341
char * late_string(void *obj, attrsym_t *attr, char *defaultValue)
Definition utils.c:84
int late_int(void *obj, attrsym_t *attr, int defaultValue, int minimum)
Definition utils.c:39
pointf Bezier(const pointf *V, double t, pointf *Left, pointf *Right)
Definition utils.c:174
bool overlap_label(textlabel_t *lp, boxf b)
Definition utils.c:1322
double late_double(void *obj, attrsym_t *attr, double defaultValue, double minimum)
Definition utils.c:54
pointf dotneato_closest(splines *spl, pointf pt)
Definition utils.c:346
char * latin1ToUTF8(char *s)
Converts string from Latin1 encoding to utf8. Also translates HTML entities.
Definition utils.c:1259
char * htmlEntityUTF8(char *s, graph_t *g)
Definition utils.c:1182
#define DEFAULT_SELECTEDFILLCOLOR
Definition const.h:53
#define VTX
Definition const.h:132
#define CHAR_LATIN1
Definition const.h:188
#define DEFAULT_LAYERLISTSEP
Definition const.h:83
#define NO_SUPPORT
Definition const.h:138
#define METAPOST
Definition const.h:133
#define PCL
Definition const.h:129
#define DEFAULT_COLOR
Definition const.h:48
#define QPDF
Definition const.h:135
#define DEFAULT_EMBED_MARGIN
Definition const.h:94
#define DEFAULT_ACTIVEFILLCOLOR
Definition const.h:50
#define MIF
Definition const.h:130
#define DEFAULT_FONTSIZE
Definition const.h:61
#define MIN_FONTSIZE
Definition const.h:63
#define DEFAULT_PRINT_MARGIN
Definition const.h:92
#define DEFAULT_FONTNAME
Definition const.h:67
#define GVRENDER_PLUGIN
Definition const.h:137
#define DEFAULT_FILL
Definition const.h:69
#define DEFAULT_DELETEDFILLCOLOR
Definition const.h:56
#define DFLT_SAMPLE
Definition const.h:105
#define DEFAULT_SELECTEDPENCOLOR
Definition const.h:52
#define DEFAULT_LAYERSEP
Definition const.h:82
#define GRADIENT
Definition const.h:223
#define DEFAULT_VISITEDFILLCOLOR
Definition const.h:59
#define DEFAULT_DELETEDPENCOLOR
Definition const.h:55
#define DEFAULT_GRAPH_PAD
Definition const.h:96
#define RGRADIENT
Definition const.h:224
#define DEFAULT_VISITEDPENCOLOR
Definition const.h:58
#define DEFAULT_ACTIVEPENCOLOR
Definition const.h:49
helpers for verbose/debug printing
static float dy
Definition draw.c:43
static float dx
Definition draw.c:42
#define left
Definition dthdr.h:12
static void ins(Dict_t *d, Dtlink_t **set, Agedge_t *e)
Definition edge.c:151
static void del(Dict_t *d, Dtlink_t **set, Agedge_t *e)
Definition edge.c:158
Ppolyline_t * ellipticWedge(pointf ctr, double xsemi, double ysemi, double angle0, double angle1)
Definition ellipse.c:274
#define EPSILON
Definition emit.c:55
static void render_corner_arc(GVJ_t *job, const Ppolyline_t *wedge, size_t arc_start_idx, size_t arc_point_count, char *edge_color)
Definition emit.c:2298
bool emit_once(char *str)
Definition emit.c:3726
static size_t find_prev_distinct(const pointf *pts, size_t i)
Definition emit.c:2100
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:2052
static void map_point(GVJ_t *job, pointf pf)
Definition emit.c:342
static radfunc_t taperfun(edge_t *e)
Definition emit.c:2084
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:3580
static void emit_end_cluster(GVJ_t *job)
Definition emit.c:3767
static int compare_corners(const void *a, const void *b)
Definition emit.c:2139
static void emit_edge_graphics(GVJ_t *job, edge_t *e, char **styles)
Definition emit.c:2349
void emit_clusters(GVJ_t *job, Agraph_t *g, int flags)
Definition emit.c:3772
static Dict_t * strings
Definition emit.c:3720
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:3426
static double revfunc(double curlen, double totallen, double initwid)
Definition emit.c:2063
static void nodeIntersect(GVJ_t *job, pointf p, bool explicit_iurl, char *iurl, bool explicit_itooltip)
Definition emit.c:2940
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:2151
static char * defaultlinestyle[3]
Definition emit.c:105
static void init_splines_bb(splines *spl)
Definition emit.c:4096
void gv_fixLocale(int set)
Definition emit.c:4172
static void freePara(xdot_op *xop)
Definition emit.c:3135
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:4152
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:2058
#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:4122
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:4071
static bool is_style_delim(int c)
Definition emit.c:3944
static void expandBB(boxf *bb, pointf p)
Definition emit.c:3080
static void find_ortho_corners(const pointf *pts, size_t n, double radius, corners_t *corners)
Definition emit.c:2259
bool findStopColor(const char *colorlist, char *clrs[2], double *frac)
Definition emit.c:4330
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:3030
static const char adjust[]
Definition emit.c:3077
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:3721
static void init_bb_node(graph_t *g, node_t *n)
Definition emit.c:4131
#define NotFirstPage(j)
Definition emit.c:3586
static double bothfunc(double curlen, double totallen, double initwid)
Definition emit.c:2076
static int multicolor(GVJ_t *job, edge_t *e, char **styles, const char *colors, double arrowsize, double penwidth)
Definition emit.c:1974
#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:3996
static void draw_ortho_corner_markers(GVJ_t *job, const corners_t *corners, double radius, char *edge_color)
Definition emit.c:2323
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:3503
#define SID
Definition emit.c:3957
#define THIN_LINE
Definition emit.c:538
static boxf textBB(double x, double y, textspan_t *span)
Definition emit.c:3111
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:3330
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:1920
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:2881
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:3306
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:3353
static void emit_colors(GVJ_t *job, graph_t *g)
Definition emit.c:3448
#define SEP
static void emit_begin_cluster(GVJ_t *job, Agraph_t *sg)
Definition emit.c:3753
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:3218
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:4194
static void emit_page(GVJ_t *job, graph_t *g)
Definition emit.c:3588
gvevent_key_binding_t gvevent_key_binding[]
Definition gvevent.c:521
char ** parse_style(char *s)
Definition emit.c:4005
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:3741
void emit_map_rect(GVJ_t *job, boxf b)
Definition emit.c:640
static void emit_end_edge(GVJ_t *job)
Definition emit.c:2963
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:2068
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:3088
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:2685
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:2705
static token_t style_token(char **s)
Definition emit.c:3969
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:3566
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:3667
boxf xdotBB(Agraph_t *g)
Definition emit.c:3141
double(* radfunc_t)(double, double, double)
Definition emit.c:2056
static size_t find_next_distinct(const pointf *pts, size_t i, size_t n)
Definition emit.c:2116
static void init_job_pad(GVJ_t *job)
Definition emit.c:3287
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:63
void rect2poly(pointf *p)
Definition geom.c:122
double ptToLine2(pointf a, pointf b, pointf p)
Definition geom.c:181
#define POINTS(a_inches)
Definition geom.h:62
struct pointf_s pointf
#define DIST(p, q)
Definition geom.h:56
#define POINTS_PER_INCH
Definition geom.h:58
geometric functions (e.g. on points and boxes)
static void expandbp(boxf *b, pointf p)
expand box b as needed to enclose point p
Definition geomprocs.h:45
static WUR point exch_xy(point p)
Definition geomprocs.h:120
static WUR pointf mid_pointf(pointf p, pointf q)
Definition geomprocs.h:104
static WUR pointf sub_pointf(pointf p, pointf q)
Definition geomprocs.h:96
static WUR pointf add_pointf(pointf p, pointf q)
Definition geomprocs.h:88
static WUR pointf exch_xyf(pointf p)
Definition geomprocs.h:128
#define EXPANDBB(b0, b1)
Definition geomprocs.h:65
static WUR pointf scale(double c, pointf p)
Definition geomprocs.h:148
static WUR bool boxf_overlap(boxf b0, boxf b1)
Definition geomprocs.h:136
show_boxes_t Show_boxes
Definition globals.c:22
Agsym_t * N_fontsize
Definition globals.h:75
Agsym_t * N_layer
Definition globals.h:78
Agsym_t * E_comment
Definition globals.h:86
Agsym_t * G_peripheries
Definition globals.h:71
Agsym_t * E_decorate
Definition globals.h:84
Agsym_t * E_style
Definition globals.h:84
Agsym_t * N_fontname
Definition globals.h:75
Agsym_t * N_comment
Definition globals.h:79
Agsym_t * N_style
Definition globals.h:76
Agsym_t * E_fillcolor
Definition globals.h:82
Agsym_t * E_dir
Definition globals.h:84
Agsym_t * E_color
Definition globals.h:82
Agsym_t * E_arrowsz
Definition globals.h:85
Agsym_t * G_gradientangle
Definition globals.h:72
Agsym_t * G_penwidth
Definition globals.h:71
bool Y_invert
invert y in dot & plain output
Definition globals.h:67
Agsym_t * E_penwidth
Definition globals.h:92
Agsym_t * E_layer
Definition globals.h:85
static double len(glCompPoint p)
Definition glutils.c:138
static bool Verbose
Definition gml2gv.c:26
void free(void *)
#define FILL
Definition gmlparse.h:119
edge
Definition gmlparse.y: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:198
Agsym_t * agattr_text(Agraph_t *g, int kind, char *name, const char *value)
creates or looks up text attributes of a graph
Definition attr.c:336
char * agget(void *obj, char *name)
Definition attr.c:450
char * agxget(void *obj, Agsym_t *sym)
Definition attr.c:460
#define ED_xlabel(e)
Definition types.h:590
#define ED_head_label(e)
Definition types.h:587
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:28
#define ED_gui_state(e)
Definition types.h:586
#define ED_spl(e)
Definition types.h:595
#define agtail(e)
Definition cgraph.h:977
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:98
#define ED_tail_label(e)
Definition types.h:596
#define aghead(e)
Definition cgraph.h:978
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:43
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:89
#define ED_label(e)
Definition types.h:589
void agwarningf(const char *fmt,...)
Definition agerror.c:175
void agerrorf(const char *fmt,...)
Definition agerror.c:167
int agerr(agerrlevel_t level, const char *fmt,...)
Definition agerror.c:157
@ AGPREV
Definition cgraph.h:946
#define agfindgraphattr(g, a)
Definition types.h:613
#define GD_drawing(g)
Definition types.h:353
int agisdirected(Agraph_t *g)
Definition graph.c:178
#define GD_clust(g)
Definition types.h:360
#define GD_bb(g)
Definition types.h:354
#define GD_n_cluster(g)
Definition types.h:389
#define GD_label(g)
Definition types.h:374
#define GD_charset(g)
Definition types.h:367
#define GD_gvc(g)
Definition types.h:355
#define GD_odim(g)
Definition types.h:391
#define GD_gui_state(g)
Definition types.h:366
#define ND_ht(n)
Definition types.h:500
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:50
#define ND_bb(n)
Definition types.h:488
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:43
#define ND_state(n)
Definition types.h:531
#define ND_label(n)
Definition types.h:502
#define ND_rw(n)
Definition types.h:525
#define ND_lw(n)
Definition types.h:506
#define ND_xlabel(n)
Definition types.h:503
#define ND_shape_info(n)
Definition types.h:529
#define ND_pos(n)
Definition types.h:520
#define agfindnode(g, n)
Definition types.h:611
#define ND_coord(n)
Definition types.h:490
#define ND_shape(n)
Definition types.h:528
Agraph_t * agraphof(void *obj)
Definition obj.c:187
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:145
int agcontains(Agraph_t *, void *obj)
returns non-zero if obj is a member of (sub)graph
Definition obj.c:235
int agobjkind(void *obj)
Definition obj.c:254
Agraph_t * agroot(void *obj)
Definition obj.c:170
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
#define LAYOUT_DONE(g)
Definition gvc.h:52
int gvRenderJobs(GVC_t *gvc, graph_t *g)
Definition emit.c:4197
static uint64_t id
Definition gv2gml.c:42
static GVC_t * gvc
Definition gv.cpp:27
replacements for ctype.h functions
static bool gv_isdigit(int c)
Definition gv_ctype.h:41
static bool gv_isspace(int c)
Definition gv_ctype.h:55
Arithmetic helper functions.
static bool is_exactly_zero(double v)
is a value precisely 0.0?
Definition gv_math.h:67
#define SWAP(a, b)
Definition gv_math.h:134
Graphviz context library.
#define EMIT_CLUSTERS_LAST
Definition gvcjob.h:84
@ PEN_SOLID
Definition gvcjob.h:35
#define EMIT_SORTED
Definition gvcjob.h:82
#define GVRENDER_DOES_TOOLTIPS
Definition gvcjob.h:103
#define GVRENDER_DOES_Z
Definition gvcjob.h:105
#define EMIT_EDGE_SORTED
Definition gvcjob.h:86
#define GVRENDER_DOES_LABELS
Definition gvcjob.h:96
#define GVRENDER_NO_WHITE_BG
Definition gvcjob.h:106
@ MAP_CIRCLE
Definition gvcjob.h:166
@ MAP_POLYGON
Definition gvcjob.h:166
@ MAP_RECTANGLE
Definition gvcjob.h:166
#define GVDEVICE_DOES_PAGES
Definition gvcjob.h:87
#define GVDEVICE_DOES_LAYERS
Definition gvcjob.h:88
#define GVDEVICE_DOES_TRUECOLOR
Definition gvcjob.h:90
@ FILL_NONE
Definition gvcjob.h:36
#define EMIT_PREORDER
Definition gvcjob.h:85
emit_state_t
Definition gvcjob.h:173
@ EMIT_CDRAW
Definition gvcjob.h:174
@ EMIT_NDRAW
Definition gvcjob.h:176
@ EMIT_TDRAW
Definition gvcjob.h:174
@ EMIT_HDRAW
Definition gvcjob.h:174
@ EMIT_HLABEL
Definition gvcjob.h:175
@ EMIT_GDRAW
Definition gvcjob.h:174
@ EMIT_NLABEL
Definition gvcjob.h:176
@ EMIT_GLABEL
Definition gvcjob.h:175
@ EMIT_ELABEL
Definition gvcjob.h:176
@ EMIT_EDRAW
Definition gvcjob.h:176
@ EMIT_CLABEL
Definition gvcjob.h:175
@ EMIT_TLABEL
Definition gvcjob.h:175
#define GVDEVICE_EVENTS
Definition gvcjob.h:89
#define GVRENDER_DOES_MAPS
Definition gvcjob.h:97
#define GVRENDER_DOES_TARGETS
Definition gvcjob.h:104
@ CLUSTER_OBJTYPE
Definition gvcjob.h:168
@ EDGE_OBJTYPE
Definition gvcjob.h:168
@ ROOTGRAPH_OBJTYPE
Definition gvcjob.h:168
@ NODE_OBJTYPE
Definition gvcjob.h:168
#define PENWIDTH_NORMAL
Definition gvcjob.h:40
#define GVRENDER_DOES_TRANSFORM
Definition gvcjob.h:95
#define GVRENDER_Y_GOES_DOWN
Definition gvcjob.h:94
#define EMIT_COLORS
Definition gvcjob.h:83
#define GVRENDER_DOES_MAP_POLYGON
Definition gvcjob.h:100
#define GVRENDER_DOES_MAP_RECTANGLE
Definition gvcjob.h:98
static void color(Agraph_t *g)
Definition gvcolor.c:118
void gvrender_end_nodes(GVJ_t *job)
Definition gvrender.c:305
void gvrender_begin_nodes(GVJ_t *job)
Definition gvrender.c:295
void gvrender_end_job(GVJ_t *job)
Definition gvrender.c:116
void gvrender_beziercurve(GVJ_t *job, pointf *AF, size_t n, int filled)
Definition gvrender.c:579
void gvrender_comment(GVJ_t *job, char *str)
Definition gvrender.c:613
void gvrender_end_graph(GVJ_t *job)
Definition gvrender.c:225
void gvrender_set_style(GVJ_t *job, char **s)
Definition gvrender.c:481
void gvrender_set_fillcolor(GVJ_t *job, char *name)
Definition gvrender.c:450
void gvrender_polyline(GVJ_t *job, pointf *AF, size_t n)
Definition gvrender.c:596
void gvrender_begin_edges(GVJ_t *job)
Definition gvrender.c:315
pointf * gvrender_ptf_A(GVJ_t *job, pointf *af, pointf *AF, size_t n)
Definition gvrender.c:154
void gvrender_polygon(GVJ_t *job, pointf *af, size_t n, int filled)
Definition gvrender.c:537
void gvrender_end_edges(GVJ_t *job)
Definition gvrender.c:325
void gvrender_begin_graph(GVJ_t *job)
Definition gvrender.c:215
void gvrender_end_page(GVJ_t *job)
Definition gvrender.c:246
void gvrender_end_layer(GVJ_t *job)
Definition gvrender.c:267
void gvrender_box(GVJ_t *job, boxf BF, int filled)
Definition gvrender.c:565
void gvrender_begin_cluster(GVJ_t *job)
Definition gvrender.c:277
void gvrender_set_gradient_vals(GVJ_t *job, char *stopcolor, int angle, double frac)
Definition gvrender.c:467
void gvrender_ellipse(GVJ_t *job, pointf *AF, int filled)
Definition gvrender.c:520
void gvrender_end_edge(GVJ_t *job)
Definition gvrender.c:363
void gvrender_begin_anchor(GVJ_t *job, char *href, char *tooltip, char *target, char *id)
Definition gvrender.c:373
void gvrender_end_anchor(GVJ_t *job)
Definition gvrender.c:384
void gvrender_begin_layer(GVJ_t *job)
Definition gvrender.c:256
void gvrender_begin_page(GVJ_t *job)
Definition gvrender.c:236
void gvrender_begin_edge(GVJ_t *job)
Definition gvrender.c:354
GVJ_t * gvjobs_first(GVC_t *gvc)
Definition gvjobs.c:86
void gvrender_textspan(GVJ_t *job, pointf p, textspan_t *span)
Definition gvrender.c:414
void gvrender_begin_node(GVJ_t *job)
Definition gvrender.c:335
int gvrender_begin_job(GVJ_t *job)
Definition gvrender.c:103
void gvrender_end_cluster(GVJ_t *job)
Definition gvrender.c:286
GVJ_t * gvjobs_next(GVC_t *gvc)
Definition gvjobs.c:91
void gvrender_set_penwidth(GVJ_t *job, double penwidth)
Definition gvrender.c:798
int gvrender_select(GVJ_t *job, const char *lang)
Definition gvrender.c:40
void gvrender_end_node(GVJ_t *job)
Definition gvrender.c:344
void gvrender_set_pencolor(GVJ_t *job, char *name)
Definition gvrender.c:433
agxbput(xb, staging)
static xdot_state_t * xd
static double penwidth[]
$2 prev
Definition htmlparse.y:291
textitem scanner parser str
Definition htmlparse.y:218
char * strdup_and_subst_obj(char *str, void *obj)
Processes graph object escape sequences; also collapses \ to .
Definition labels.c:387
void free_textspan(textspan_t *tl, size_t cnt)
Definition labels.c:191
void emit_label(GVJ_t *job, emit_state_t emit_state, textlabel_t *lp)
Definition labels.c:217
type-generic dynamically expanding list
#define LIST_DETACH(list, datap, sizep)
Definition list.h: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:53
static void add_point(int *n, int igrp, double **x, int *nmax, double point[], int **groups)
Definition make_map.c:1207
#define delta
Definition maze.c:136
void freePath(Ppolyline_t *p)
Definition util.c:19
#define PRISIZE_T
Definition prisize_t.h:25
void round_corners(GVJ_t *job, pointf *AF, size_t sides, graphviz_polygon_style_t style, int filled)
Handle some special graphical cases, such as rounding the shape, adding diagonals at corners,...
Definition shapes.c:709
pointf textspan_size(GVC_t *gvc, textspan_t *span)
Estimates size of a textspan, in points.
Definition textspan.c:73
shape_kind shapeOf(node_t *)
Definition shapes.c:1908
pointf coord(node_t *n)
Definition utils.c:156
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:2132
pointf trunc_next
Definition emit.c:2133
pointf wedge_center
Definition emit.c:2134
size_t idx
Definition emit.c:2131
double angle1
Definition emit.c:2135
double angle2
Definition emit.c:2135
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:3965
int type
Token category.
Definition emit.c:3964
size_t size
Number of bytes in the token content.
Definition emit.c:3966
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:23
void start_timer(void)
Definition timing.c:21
String tokenization.
static strview_t tok_get(const tok_t *t)
get the current token
Definition tokenize.h:76
static tok_t tok(const char *input, const char *separators)
begin tokenization of a new string
Definition tokenize.h:43
static bool tok_end(const tok_t *t)
is this tokenizer exhausted?
Definition tokenize.h:68
static void tok_next(tok_t *t)
advance to the next token in the string being scanned
Definition tokenize.h:85
#define GUI_STATE_ACTIVE
Definition types.h:256
@ SH_POINT
Definition types.h:187
@ SH_POLY
Definition types.h:187
#define GUI_STATE_SELECTED
Definition types.h:257
#define GUI_STATE_DELETED
Definition types.h:259
#define GUI_STATE_VISITED
Definition types.h:258
Definition grammar.c:90
#define UNREACHABLE()
Definition unreachable.h:30
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25
int(* pf)(void *, char *,...)
Definition xdot.c:398
int statXDot(xdot *x, xdot_stats *sp)
Definition xdot.c:780
xdot * parseXDotF(char *s, drawfunc_t fns[], size_t sz)
Definition xdot.c:392
parsing and deparsing of xdot operations
@ xd_radial
Definition xdot.h:46
@ xd_filled_polygon
Definition xdot.h:111
@ xd_pen_color
Definition xdot.h:118
@ xd_unfilled_ellipse
Definition xdot.h:110
@ xd_fontchar
Definition xdot.h:124
@ xd_font
Definition xdot.h:119
@ xd_fill_color
Definition xdot.h:117
@ xd_unfilled_bezier
Definition xdot.h:114
@ xd_grad_fill_color
Definition xdot.h:122
@ xd_polyline
Definition xdot.h:115
@ xd_text
Definition xdot.h:116
@ xd_filled_ellipse
Definition xdot.h:109
@ xd_image
Definition xdot.h:121
@ xd_unfilled_polygon
Definition xdot.h:112
@ xd_grad_pen_color
Definition xdot.h:123
@ xd_filled_bezier
Definition xdot.h:113
@ xd_style
Definition xdot.h:120