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