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