|
Graphviz
2.29.20120524.0446
|
00001 /* $id: shapes.c,v 1.82 2007/12/24 04:50:36 ellson Exp $ $Revision$ */ 00002 /* vim:set shiftwidth=4 ts=8: */ 00003 00004 /************************************************************************* 00005 * Copyright (c) 2011 AT&T Intellectual Property 00006 * All rights reserved. This program and the accompanying materials 00007 * are made available under the terms of the Eclipse Public License v1.0 00008 * which accompanies this distribution, and is available at 00009 * http://www.eclipse.org/legal/epl-v10.html 00010 * 00011 * Contributors: See CVS logs. Details at http://www.graphviz.org/ 00012 *************************************************************************/ 00013 00014 #include "render.h" 00015 #include "htmltable.h" 00016 #include <limits.h> 00017 00018 #define RBCONST 12 00019 #define RBCURVE .5 00020 00021 static port Center = { {0, 0}, -1, 0, 0, 0, 1, 0, 0, 0 }; 00022 00023 #define ATTR_SET(a,n) ((a) && (*(agxget(n,a->index)) != '\0')) 00024 /* Default point size = 0.05 inches or 3.6 points */ 00025 #define DEF_POINT 0.05 00026 /* Minimum point size = 0.0003 inches or 0.02 points 00027 * This will make the radius 0.01 points, which is the smallest 00028 * non-zero number output by gvprintdouble in gvdevice.c 00029 */ 00030 #define MIN_POINT 0.0003 00031 /* extra null character needed to avoid style emitter from thinking 00032 * there are arguments. 00033 */ 00034 static char *point_style[3] = { "invis\0", "filled\0", 0 }; 00035 00036 /* forward declarations of functions used in shapes tables */ 00037 00038 static void poly_init(node_t * n); 00039 static void poly_free(node_t * n); 00040 static port poly_port(node_t * n, char *portname, char *); 00041 static boolean poly_inside(inside_t * inside_context, pointf p); 00042 static int poly_path(node_t * n, port * p, int side, boxf rv[], int *kptr); 00043 static void poly_gencode(GVJ_t * job, node_t * n); 00044 00045 static void record_init(node_t * n); 00046 static void record_free(node_t * n); 00047 static port record_port(node_t * n, char *portname, char *); 00048 static boolean record_inside(inside_t * inside_context, pointf p); 00049 static int record_path(node_t * n, port * p, int side, boxf rv[], 00050 int *kptr); 00051 static void record_gencode(GVJ_t * job, node_t * n); 00052 00053 static void point_init(node_t * n); 00054 static void point_gencode(GVJ_t * job, node_t * n); 00055 static boolean point_inside(inside_t * inside_context, pointf p); 00056 00057 static boolean epsf_inside(inside_t * inside_context, pointf p); 00058 static void epsf_gencode(GVJ_t * job, node_t * n); 00059 00060 /* polygon descriptions. "polygon" with 0 sides takes all user control */ 00061 00062 /* regul perip sides orien disto skew */ 00063 static polygon_t p_polygon = { FALSE, 1, 0, 0., 0., 0. }; 00064 00065 /* builtin polygon descriptions */ 00066 static polygon_t p_ellipse = { FALSE, 1, 1, 0., 0., 0. }; 00067 static polygon_t p_circle = { TRUE, 1, 1, 0., 0., 0. }; 00068 static polygon_t p_egg = { FALSE, 1, 1, 0., -.3, 0. }; 00069 static polygon_t p_triangle = { FALSE, 1, 3, 0., 0., 0. }; 00070 static polygon_t p_box = { FALSE, 1, 4, 0., 0., 0. }; 00071 static polygon_t p_square = { TRUE, 1, 4, 0., 0., 0. }; 00072 static polygon_t p_plaintext = { FALSE, 0, 4, 0., 0., 0. }; 00073 static polygon_t p_diamond = { FALSE, 1, 4, 45., 0., 0. }; 00074 static polygon_t p_trapezium = { FALSE, 1, 4, 0., -.4, 0. }; 00075 static polygon_t p_parallelogram = { FALSE, 1, 4, 0., 0., .6 }; 00076 static polygon_t p_house = { FALSE, 1, 5, 0., -.64, 0. }; 00077 static polygon_t p_pentagon = { FALSE, 1, 5, 0., 0., 0. }; 00078 static polygon_t p_hexagon = { FALSE, 1, 6, 0., 0., 0. }; 00079 static polygon_t p_septagon = { FALSE, 1, 7, 0., 0., 0. }; 00080 static polygon_t p_octagon = { FALSE, 1, 8, 0., 0., 0. }; 00081 static polygon_t p_note = { FALSE, 1, 4, 0., 0., 0., DOGEAR }; 00082 static polygon_t p_tab = { FALSE, 1, 4, 0., 0., 0., TAB }; 00083 static polygon_t p_folder = { FALSE, 1, 4, 0., 0., 0., FOLDER }; 00084 static polygon_t p_box3d = { FALSE, 1, 4, 0., 0., 0., BOX3D }; 00085 static polygon_t p_component = { FALSE, 1, 4, 0., 0., 0., COMPONENT }; 00086 00087 /* redundant and undocumented builtin polygons */ 00088 static polygon_t p_doublecircle = { TRUE, 2, 1, 0., 0., 0. }; 00089 static polygon_t p_invtriangle = { FALSE, 1, 3, 180., 0., 0. }; 00090 static polygon_t p_invtrapezium = { FALSE, 1, 4, 180., -.4, 0. }; 00091 static polygon_t p_invhouse = { FALSE, 1, 5, 180., -.64, 0. }; 00092 static polygon_t p_doubleoctagon = { FALSE, 2, 8, 0., 0., 0. }; 00093 static polygon_t p_tripleoctagon = { FALSE, 3, 8, 0., 0., 0. }; 00094 static polygon_t p_Mdiamond = 00095 { FALSE, 1, 4, 45., 0., 0., DIAGONALS | AUXLABELS }; 00096 static polygon_t p_Msquare = { TRUE, 1, 4, 0., 0., 0., DIAGONALS }; 00097 static polygon_t p_Mcircle = 00098 { TRUE, 1, 1, 0., 0., 0., DIAGONALS | AUXLABELS }; 00099 00100 #define IS_BOX(n) (ND_shape(n)->polygon == &p_box) 00101 00102 /* True if style requires processing through round_corners. */ 00103 #define SPECIAL_CORNERS(style) \ 00104 ((style) & (ROUNDED | DIAGONALS | DOGEAR | TAB | FOLDER | BOX3D | COMPONENT)) 00105 00106 /* 00107 * every shape has these functions: 00108 * 00109 * void SHAPE_init(node_t *n) 00110 * initialize the shape (usually at least its size). 00111 * void SHAPE_free(node_t *n) 00112 * free all memory used by the shape 00113 * port SHAPE_port(node_t *n, char *portname) 00114 * return the aiming point and slope (if constrained) 00115 * of a port. 00116 * int SHAPE_inside(inside_t *inside_context, pointf p, edge_t *e); 00117 * test if point is inside the node shape which is 00118 * assumed convex. 00119 * the point is relative to the node center. the edge 00120 * is passed in case the port affects spline clipping. 00121 * int SHAPE_path(node *n, edge_t *e, int pt, boxf path[], int *nbox) 00122 * create a path for the port of e that touches n, 00123 * return side 00124 * void SHAPE_gencode(GVJ_t *job, node_t *n) 00125 * generate graphics code for a node. 00126 * 00127 * some shapes, polygons in particular, use additional shape control data * 00128 * 00129 */ 00130 00131 static shape_functions poly_fns = { 00132 poly_init, 00133 poly_free, 00134 poly_port, 00135 poly_inside, 00136 poly_path, 00137 poly_gencode 00138 }; 00139 static shape_functions point_fns = { 00140 point_init, 00141 poly_free, 00142 poly_port, 00143 point_inside, 00144 NULL, 00145 point_gencode 00146 }; 00147 static shape_functions record_fns = { 00148 record_init, 00149 record_free, 00150 record_port, 00151 record_inside, 00152 record_path, 00153 record_gencode 00154 }; 00155 static shape_functions epsf_fns = { 00156 epsf_init, 00157 epsf_free, 00158 poly_port, 00159 epsf_inside, 00160 NULL, 00161 epsf_gencode 00162 }; 00163 00164 static shape_desc Shapes[] = { /* first entry is default for no such shape */ 00165 {"box", &poly_fns, &p_box}, 00166 {"polygon", &poly_fns, &p_polygon}, 00167 {"ellipse", &poly_fns, &p_ellipse}, 00168 {"oval", &poly_fns, &p_ellipse}, 00169 {"circle", &poly_fns, &p_circle}, 00170 {"point", &point_fns, &p_circle}, 00171 {"egg", &poly_fns, &p_egg}, 00172 {"triangle", &poly_fns, &p_triangle}, 00173 {"none", &poly_fns, &p_plaintext}, 00174 {"plaintext", &poly_fns, &p_plaintext}, 00175 {"diamond", &poly_fns, &p_diamond}, 00176 {"trapezium", &poly_fns, &p_trapezium}, 00177 {"parallelogram", &poly_fns, &p_parallelogram}, 00178 {"house", &poly_fns, &p_house}, 00179 {"pentagon", &poly_fns, &p_pentagon}, 00180 {"hexagon", &poly_fns, &p_hexagon}, 00181 {"septagon", &poly_fns, &p_septagon}, 00182 {"octagon", &poly_fns, &p_octagon}, 00183 {"note", &poly_fns, &p_note}, 00184 {"tab", &poly_fns, &p_tab}, 00185 {"folder", &poly_fns, &p_folder}, 00186 {"box3d", &poly_fns, &p_box3d}, 00187 {"component", &poly_fns, &p_component}, 00188 {"rect", &poly_fns, &p_box}, 00189 {"rectangle", &poly_fns, &p_box}, 00190 {"square", &poly_fns, &p_square}, 00191 {"doublecircle", &poly_fns, &p_doublecircle}, 00192 {"doubleoctagon", &poly_fns, &p_doubleoctagon}, 00193 {"tripleoctagon", &poly_fns, &p_tripleoctagon}, 00194 {"invtriangle", &poly_fns, &p_invtriangle}, 00195 {"invtrapezium", &poly_fns, &p_invtrapezium}, 00196 {"invhouse", &poly_fns, &p_invhouse}, 00197 {"Mdiamond", &poly_fns, &p_Mdiamond}, 00198 {"Msquare", &poly_fns, &p_Msquare}, 00199 {"Mcircle", &poly_fns, &p_Mcircle}, 00200 /* *** shapes other than polygons *** */ 00201 {"record", &record_fns, NULL}, 00202 {"Mrecord", &record_fns, NULL}, 00203 {"epsf", &epsf_fns, NULL}, 00204 {NULL, NULL, NULL} 00205 }; 00206 00207 static void unrecognized(node_t * n, char *p) 00208 { 00209 agerr(AGWARN, "node %s, port %s unrecognized\n", agnameof(n), p); 00210 } 00211 00212 static double quant(double val, double q) 00213 { 00214 int i; 00215 i = val / q; 00216 if (i * q + .00001 < val) 00217 i++; 00218 return i * q; 00219 } 00220 00221 /* test if both p0 and p1 are on the same side of the line L0,L1 */ 00222 static int same_side(pointf p0, pointf p1, pointf L0, pointf L1) 00223 { 00224 int s0, s1; 00225 double a, b, c; 00226 00227 /* a x + b y = c */ 00228 a = -(L1.y - L0.y); 00229 b = (L1.x - L0.x); 00230 c = a * L0.x + b * L0.y; 00231 00232 s0 = (a * p0.x + b * p0.y - c >= 0); 00233 s1 = (a * p1.x + b * p1.y - c >= 0); 00234 return (s0 == s1); 00235 } 00236 00237 static 00238 void pencolor(GVJ_t * job, node_t * n) 00239 { 00240 char *color; 00241 00242 color = late_nnstring(n, N_color, ""); 00243 if (color[0]) 00244 gvrender_set_pencolor(job, color); 00245 else 00246 gvrender_set_pencolor(job, DEFAULT_COLOR); 00247 } 00248 00249 static 00250 char *findFillDflt(node_t * n, char *dflt) 00251 { 00252 char *color; 00253 00254 color = late_nnstring(n, N_fillcolor, ""); 00255 if (!color[0]) { 00256 /* for backward compatibilty, default fill is same as pen */ 00257 color = late_nnstring(n, N_color, ""); 00258 if (!color[0]) { 00259 color = dflt; 00260 } 00261 } 00262 return color; 00263 } 00264 00265 static 00266 char *findFill(node_t * n) 00267 { 00268 return (findFillDflt(n, DEFAULT_FILL)); 00269 } 00270 00271 char *findAttrColor(void *obj, attrsym_t *colorattr, char *dflt){ 00272 char *color; 00273 00274 if(colorattr != NULL) 00275 color = late_nnstring(obj, colorattr, dflt); 00276 else if(dflt != NULL && dflt[0]) 00277 color = dflt; 00278 else 00279 color = DEFAULT_FILL; 00280 return color; 00281 } 00282 00283 static char **checkStyle(node_t * n, int *flagp) 00284 { 00285 char *style; 00286 char **pstyle = 0; 00287 int istyle = 0; 00288 polygon_t *poly; 00289 00290 style = late_nnstring(n, N_style, ""); 00291 if (style[0]) { 00292 char **pp; 00293 char **qp; 00294 char *p; 00295 pp = pstyle = parse_style(style); 00296 while ((p = *pp)) { 00297 if (streq(p, "filled")) { 00298 istyle |= FILLED; 00299 pp++; 00300 } else if (streq(p, "rounded")) { 00301 istyle |= ROUNDED; 00302 qp = pp; /* remove rounded from list passed to renderer */ 00303 do { 00304 qp++; 00305 *(qp - 1) = *qp; 00306 } while (*qp); 00307 } else if (streq(p, "diagonals")) { 00308 istyle |= DIAGONALS; 00309 qp = pp; /* remove diagonals from list passed to renderer */ 00310 do { 00311 qp++; 00312 *(qp - 1) = *qp; 00313 } while (*qp); 00314 } else if (streq(p, "invis")) { 00315 istyle |= INVISIBLE; 00316 pp++; 00317 } else if (streq(p, "radial")) { 00318 istyle |= (RADIAL|FILLED); 00319 qp = pp; /* remove radial from list passed to renderer */ 00320 do { 00321 qp++; 00322 *(qp - 1) = *qp; 00323 } while (*qp); 00324 } else 00325 pp++; 00326 } 00327 } 00328 if ((poly = ND_shape(n)->polygon)) 00329 istyle |= poly->option; 00330 00331 *flagp = istyle; 00332 return pstyle; 00333 } 00334 00335 static int stylenode(GVJ_t * job, node_t * n) 00336 { 00337 char **pstyle, *s; 00338 int istyle; 00339 double penwidth; 00340 00341 if ((pstyle = checkStyle(n, &istyle))) 00342 gvrender_set_style(job, pstyle); 00343 00344 #ifndef WITH_CGRAPH 00345 if (N_penwidth && ((s = agxget(n, N_penwidth->index)) && s[0])) { 00346 #else 00347 if (N_penwidth && ((s = agxget(n, N_penwidth)) && s[0])) { 00348 #endif 00349 penwidth = late_double(n, N_penwidth, 1.0, 0.0); 00350 gvrender_set_penwidth(job, penwidth); 00351 } 00352 00353 return istyle; 00354 } 00355 00356 static void Mcircle_hack(GVJ_t * job, node_t * n) 00357 { 00358 double x, y; 00359 pointf AF[2], p; 00360 00361 y = .7500; 00362 x = .6614; /* x^2 + y^2 = 1.0 */ 00363 p.y = y * ND_ht(n) / 2.0; 00364 p.x = ND_rw(n) * x; /* assume node is symmetric */ 00365 00366 AF[0] = add_pointf(p, ND_coord(n)); 00367 AF[1].y = AF[0].y; 00368 AF[1].x = AF[0].x - 2 * p.x; 00369 gvrender_polyline(job, AF, 2); 00370 AF[0].y -= 2 * p.y; 00371 AF[1].y = AF[0].y; 00372 gvrender_polyline(job, AF, 2); 00373 } 00374 00375 /* round_corners: 00376 * Handle some special graphical cases, such as rounding the shape, 00377 * adding diagonals at corners, or drawing certain non-simple figures. 00378 * Any drawing done here should assume fillcolors, pencolors, etc. 00379 * have been set by the calling routine. Normally, the drawing should 00380 * consist of a region, filled or unfilled, followed by additional line 00381 * segments. A single fill is necessary for gradient colors to work. 00382 */ 00383 void round_corners(GVJ_t * job, pointf * AF, int sides, int style, int filled) 00384 { 00385 pointf *B, C[4], *D, p0, p1; 00386 double rbconst, d, dx, dy, t; 00387 int i, seg, mode; 00388 pointf* pts; 00389 00390 if (style & DIAGONALS) 00391 mode = DIAGONALS; 00392 else if (style & (DOGEAR | TAB | FOLDER | BOX3D | COMPONENT)) 00393 mode = style & (DOGEAR | TAB | FOLDER | BOX3D | COMPONENT); 00394 else 00395 mode = ROUNDED; 00396 B = N_NEW(4 * sides + 4, pointf); 00397 i = 0; 00398 /* rbconst is distance offset from a corner of the polygon. 00399 * It should be the same for every corner, and also never 00400 * bigger than one-third the length of a side. 00401 */ 00402 rbconst = RBCONST; 00403 for (seg = 0; seg < sides; seg++) { 00404 p0 = AF[seg]; 00405 if (seg < sides - 1) 00406 p1 = AF[seg + 1]; 00407 else 00408 p1 = AF[0]; 00409 dx = p1.x - p0.x; 00410 dy = p1.y - p0.y; 00411 d = sqrt(dx * dx + dy * dy); 00412 rbconst = MIN(rbconst, d / 3.0); 00413 } 00414 for (seg = 0; seg < sides; seg++) { 00415 p0 = AF[seg]; 00416 if (seg < sides - 1) 00417 p1 = AF[seg + 1]; 00418 else 00419 p1 = AF[0]; 00420 dx = p1.x - p0.x; 00421 dy = p1.y - p0.y; 00422 d = sqrt(dx * dx + dy * dy); 00423 t = rbconst / d; 00424 if (style & (BOX3D | COMPONENT)) 00425 t /= 3; 00426 else if (style & DOGEAR) 00427 t /= 2; 00428 if (mode != ROUNDED) 00429 B[i++] = p0; 00430 else 00431 B[i++] = interpolate_pointf(RBCURVE * t, p0, p1); 00432 B[i++] = interpolate_pointf(t, p0, p1); 00433 B[i++] = interpolate_pointf(1.0 - t, p0, p1); 00434 if (mode == ROUNDED) 00435 B[i++] = interpolate_pointf(1.0 - RBCURVE * t, p0, p1); 00436 } 00437 B[i++] = B[0]; 00438 B[i++] = B[1]; 00439 B[i++] = B[2]; 00440 00441 switch (mode) { 00442 case ROUNDED: 00443 pts = N_GNEW(6 * sides + 2, pointf); 00444 i = 0; 00445 for (seg = 0; seg < sides; seg++) { 00446 pts[i++] = B[4 * seg]; 00447 pts[i++] = B[4 * seg+1]; 00448 pts[i++] = B[4 * seg+1]; 00449 pts[i++] = B[4 * seg+2]; 00450 pts[i++] = B[4 * seg+2]; 00451 pts[i++] = B[4 * seg+3]; 00452 } 00453 pts[i++] = pts[0]; 00454 pts[i++] = pts[1]; 00455 gvrender_beziercurve(job, pts+1, i-1, FALSE, FALSE, filled); 00456 free (pts); 00457 00458 #if 0 00459 if (filled) { 00460 pointf *pts = N_GNEW(2 * sides, pointf); 00461 pts[j++] = B[4 * seg + 1]; 00462 pts[j++] = B[4 * seg + 2]; 00463 } 00464 gvrender_polygon(job, pts, 2 * sides, filled); 00465 free(pts); 00466 for (seg = 0; seg < sides; seg++) { 00467 } 00468 } 00469 if (penc) { 00470 for (seg = 0; seg < sides; seg++) { 00471 gvrender_polyline(job, B + 4 * seg + 1, 2); 00472 gvrender_beziercurve(job, B + 4 * seg + 2, 4, FALSE, FALSE, FALSE); 00473 } 00474 } 00475 #endif 00476 break; 00477 case DIAGONALS: 00478 /* diagonals are weird. rewrite someday. */ 00479 gvrender_polygon(job, AF, sides, filled); 00480 00481 for (seg = 0; seg < sides; seg++) { 00482 #ifdef NOTDEF 00483 C[0] = B[3 * seg]; 00484 C[1] = B[3 * seg + 3]; 00485 gvrender_polyline(job, C, 2); 00486 #endif 00487 C[0] = B[3 * seg + 2]; 00488 C[1] = B[3 * seg + 4]; 00489 gvrender_polyline(job, C, 2); 00490 } 00491 break; 00492 case DOGEAR: 00493 /* Add the cutoff edge. */ 00494 D = N_NEW(sides + 1, pointf); 00495 for (seg = 1; seg < sides; seg++) 00496 D[seg] = AF[seg]; 00497 D[0] = B[3 * (sides - 1) + 4]; 00498 D[sides] = B[3 * (sides - 1) + 2]; 00499 gvrender_polygon(job, D, sides + 1, filled); 00500 free(D); 00501 00502 /* Draw the inner edge. */ 00503 seg = sides - 1; 00504 C[0] = B[3 * seg + 2]; 00505 C[1] = B[3 * seg + 4]; 00506 C[2].x = C[1].x + (C[0].x - B[3 * seg + 3].x); 00507 C[2].y = C[1].y + (C[0].y - B[3 * seg + 3].y); 00508 gvrender_polyline(job, C + 1, 2); 00509 C[1] = C[2]; 00510 gvrender_polyline(job, C, 2); 00511 break; 00512 case TAB: 00513 /* 00514 * Adjust the perimeter for the protrusions. 00515 * 00516 * D[3] +--+ D[2] 00517 * | | B[1] 00518 * B[3] + +----------+--+ AF[0]=B[0]=D[0] 00519 * | B[2]=D[1] | 00520 * B[4] + | 00521 * | | 00522 * B[5] + | 00523 * +----------------+ 00524 * 00525 */ 00526 /* Add the tab edges. */ 00527 D = N_NEW(sides + 2, pointf); 00528 D[0] = AF[0]; 00529 D[1] = B[2]; 00530 D[2].x = B[2].x + (B[3].x - B[4].x) / 3; 00531 D[2].y = B[2].y + (B[3].y - B[4].y) / 3; 00532 D[3].x = B[3].x + (B[3].x - B[4].x) / 3; 00533 D[3].y = B[3].y + (B[3].y - B[4].y) / 3; 00534 for (seg = 4; seg < sides + 2; seg++) 00535 D[seg] = AF[seg - 2]; 00536 gvrender_polygon(job, D, sides + 2, filled); 00537 free(D); 00538 00539 00540 /* Draw the inner edge. */ 00541 C[0] = B[3]; 00542 C[1] = B[2]; 00543 gvrender_polyline(job, C, 2); 00544 break; 00545 case FOLDER: 00546 /* 00547 * Adjust the perimeter for the protrusions. 00548 * 00549 * D[2] +----+ D[1] 00550 * B[3]= / \ 00551 * D[4] +--+----+ + + AF[0]=B[0]=D[0] 00552 * | B[2] D[3] B[1]| 00553 * B[4] + | 00554 * | | 00555 * B[5] + | 00556 * +----------------+ 00557 * 00558 */ 00559 /* Add the folder edges. */ 00560 D = N_NEW(sides + 3, pointf); 00561 D[0] = AF[0]; 00562 D[1].x = AF[0].x - (AF[0].x - B[1].x) / 4; 00563 D[1].y = AF[0].y + (B[3].y - B[4].y) / 3; 00564 D[2].x = AF[0].x - 2 * (AF[0].x - B[1].x); 00565 D[2].y = D[1].y; 00566 D[3].x = AF[0].x - 2.25 * (AF[0].x - B[1].x); 00567 D[3].y = B[3].y; 00568 D[4].x = B[3].x; 00569 D[4].y = B[3].y; 00570 for (seg = 4; seg < sides + 3; seg++) 00571 D[seg] = AF[seg - 3]; 00572 gvrender_polygon(job, D, sides + 3, filled); 00573 free(D); 00574 break; 00575 case BOX3D: 00576 assert(sides == 4); 00577 /* Adjust for the cutoff edges. */ 00578 D = N_NEW(sides + 2, pointf); 00579 D[0] = AF[0]; 00580 D[1] = B[2]; 00581 D[2] = B[4]; 00582 D[3] = AF[2]; 00583 D[4] = B[8]; 00584 D[5] = B[10]; 00585 gvrender_polygon(job, D, sides + 2, filled); 00586 free(D); 00587 00588 /* Draw the inner vertices. */ 00589 C[0].x = B[1].x + (B[11].x - B[0].x); 00590 C[0].y = B[1].y + (B[11].y - B[0].y); 00591 C[1] = B[4]; 00592 gvrender_polyline(job, C, 2); 00593 C[1] = B[8]; 00594 gvrender_polyline(job, C, 2); 00595 C[1] = B[0]; 00596 gvrender_polyline(job, C, 2); 00597 break; 00598 case COMPONENT: 00599 assert(sides == 4); 00600 /* 00601 * Adjust the perimeter for the protrusions. 00602 * 00603 * D[1] +----------------+ D[0] 00604 * | | 00605 * 3+---+2 | 00606 * | | 00607 * 4+---+5 | 00608 * | | 00609 * 7+---+6 | 00610 * | | 00611 * 8+---+9 | 00612 * | | 00613 * 10+----------------+ D[11] 00614 * 00615 */ 00616 D = N_NEW(sides + 8, pointf); 00617 D[0] = AF[0]; 00618 D[1] = AF[1]; 00619 D[2].x = B[3].x + (B[4].x - B[3].x); 00620 D[2].y = B[3].y + (B[4].y - B[3].y); 00621 D[3].x = D[2].x + (B[3].x - B[2].x); 00622 D[3].y = D[2].y + (B[3].y - B[2].y); 00623 D[4].x = D[3].x + (B[4].x - B[3].x); 00624 D[4].y = D[3].y + (B[4].y - B[3].y); 00625 D[5].x = D[4].x + (D[2].x - D[3].x); 00626 D[5].y = D[4].y + (D[2].y - D[3].y); 00627 00628 D[9].x = B[6].x + (B[5].x - B[6].x); 00629 D[9].y = B[6].y + (B[5].y - B[6].y); 00630 D[8].x = D[9].x + (B[6].x - B[7].x); 00631 D[8].y = D[9].y + (B[6].y - B[7].y); 00632 D[7].x = D[8].x + (B[5].x - B[6].x); 00633 D[7].y = D[8].y + (B[5].y - B[6].y); 00634 D[6].x = D[7].x + (D[9].x - D[8].x); 00635 D[6].y = D[7].y + (D[9].y - D[8].y); 00636 00637 D[10] = AF[2]; 00638 D[11] = AF[3]; 00639 gvrender_polygon(job, D, sides + 8, filled); 00640 00641 /* Draw the internal vertices. */ 00642 C[0] = D[2]; 00643 C[1].x = D[2].x - (D[3].x - D[2].x); 00644 C[1].y = D[2].y - (D[3].y - D[2].y); 00645 C[2].x = C[1].x + (D[4].x - D[3].x); 00646 C[2].y = C[1].y + (D[4].y - D[3].y); 00647 C[3] = D[5]; 00648 gvrender_polyline(job, C, 4); 00649 C[0] = D[6]; 00650 C[1].x = D[6].x - (D[7].x - D[6].x); 00651 C[1].y = D[6].y - (D[7].y - D[6].y); 00652 C[2].x = C[1].x + (D[8].x - D[7].x); 00653 C[2].y = C[1].y + (D[8].y - D[7].y); 00654 C[3] = D[9]; 00655 gvrender_polyline(job, C, 4); 00656 00657 free(D); 00658 break; 00659 } 00660 free(B); 00661 } 00662 00663 /*=============================poly start=========================*/ 00664 00665 /* userSize; 00666 * Return maximum size, in points, of width and height supplied 00667 * by user, if any. Return 0 otherwise. 00668 */ 00669 static double userSize(node_t * n) 00670 { 00671 double w, h; 00672 w = late_double(n, N_width, 0.0, MIN_NODEWIDTH); 00673 h = late_double(n, N_height, 0.0, MIN_NODEHEIGHT); 00674 return POINTS(MAX(w, h)); 00675 } 00676 00677 shape_kind shapeOf(node_t * n) 00678 { 00679 shape_desc *sh = ND_shape(n); 00680 void (*ifn) (node_t *); 00681 00682 if (!sh) 00683 return SH_UNSET; 00684 ifn = ND_shape(n)->fns->initfn; 00685 if (ifn == poly_init) 00686 return SH_POLY; 00687 else if (ifn == record_init) 00688 return SH_RECORD; 00689 else if (ifn == point_init) 00690 return SH_POINT; 00691 else if (ifn == epsf_init) 00692 return SH_EPSF; 00693 else 00694 return SH_UNSET; 00695 } 00696 00697 boolean isPolygon(node_t * n) 00698 { 00699 return (ND_shape(n) && (ND_shape(n)->fns->initfn == poly_init)); 00700 } 00701 00702 static void poly_init(node_t * n) 00703 { 00704 pointf dimen, min_bb, bb; 00705 point imagesize; 00706 pointf P, Q, R; 00707 pointf *vertices; 00708 char *p, *sfile; 00709 double temp, alpha, beta, gamma; 00710 double orientation, distortion, skew; 00711 double sectorangle, sidelength, skewdist, gdistortion, gskew; 00712 double angle, sinx, cosx, xmax, ymax, scalex, scaley; 00713 double width, height, marginx, marginy, spacex; 00714 int regular, peripheries, sides; 00715 int i, j, isBox, outp; 00716 polygon_t *poly = NEW(polygon_t); 00717 00718 regular = ND_shape(n)->polygon->regular; 00719 peripheries = ND_shape(n)->polygon->peripheries; 00720 sides = ND_shape(n)->polygon->sides; 00721 orientation = ND_shape(n)->polygon->orientation; 00722 skew = ND_shape(n)->polygon->skew; 00723 distortion = ND_shape(n)->polygon->distortion; 00724 regular |= mapbool(agget(n, "regular")); 00725 00726 /* all calculations in floating point POINTS */ 00727 00728 /* make x and y dimensions equal if node is regular 00729 * If the user has specified either width or height, use the max. 00730 * Else use minimum default value. 00731 * If node is not regular, use the current width and height. 00732 */ 00733 if (regular) { 00734 double sz = userSize(n); 00735 if (sz > 0.0) 00736 width = height = sz; 00737 else { 00738 width = ND_width(n); 00739 height = ND_height(n); 00740 width = height = POINTS(MIN(width, height)); 00741 } 00742 } else { 00743 width = POINTS(ND_width(n)); 00744 height = POINTS(ND_height(n)); 00745 } 00746 00747 peripheries = late_int(n, N_peripheries, peripheries, 0); 00748 orientation += late_double(n, N_orientation, 0.0, -360.0); 00749 if (sides == 0) { /* not for builtins */ 00750 skew = late_double(n, N_skew, 0.0, -100.0); 00751 sides = late_int(n, N_sides, 4, 0); 00752 distortion = late_double(n, N_distortion, 0.0, -100.0); 00753 } 00754 00755 /* get label dimensions */ 00756 dimen = ND_label(n)->dimen; 00757 00758 /* minimal whitespace around label */ 00759 if (ROUND(abs(dimen.x)) || ROUND(abs(dimen.y))) { 00760 /* padding */ 00761 if ((p = agget(n, "margin"))) { 00762 i = sscanf(p, "%lf,%lf", &marginx, &marginy); 00763 if (marginx < 0) 00764 marginx = 0; 00765 if (marginy < 0) 00766 marginy = 0; 00767 if (i > 0) { 00768 dimen.x += 2 * POINTS(marginx); 00769 if (i > 1) 00770 dimen.y += 2 * POINTS(marginy); 00771 else 00772 dimen.y += 2 * POINTS(marginx); 00773 } else 00774 PAD(dimen); 00775 } else 00776 PAD(dimen); 00777 } 00778 spacex = dimen.x - ND_label(n)->dimen.x; 00779 00780 /* quantization */ 00781 if ((temp = GD_drawing(agraphof(n))->quantum) > 0.0) { 00782 temp = POINTS(temp); 00783 dimen.x = quant(dimen.x, temp); 00784 dimen.y = quant(dimen.y, temp); 00785 } 00786 00787 imagesize.x = imagesize.y = 0; 00788 if (ND_shape(n)->usershape) { 00789 /* custom requires a shapefile 00790 * not custom is an adaptable user shape such as a postscript 00791 * function. 00792 */ 00793 if (streq(ND_shape(n)->name, "custom")) { 00794 sfile = agget(n, "shapefile"); 00795 imagesize = gvusershape_size(agraphof(n), sfile); 00796 if ((imagesize.x == -1) && (imagesize.y == -1)) { 00797 agerr(AGWARN, 00798 "No or improper shapefile=\"%s\" for node \"%s\"\n", 00799 (sfile ? sfile : "<nil>"), agnameof(n)); 00800 imagesize.x = imagesize.y = 0; 00801 } else { 00802 GD_has_images(agraphof(n)) = TRUE; 00803 imagesize.x += 2; /* some fixed padding */ 00804 imagesize.y += 2; 00805 } 00806 } 00807 } else if ((sfile = agget(n, "image")) && (*sfile != '\0')) { 00808 imagesize = gvusershape_size(agraphof(n), sfile); 00809 if ((imagesize.x == -1) && (imagesize.y == -1)) { 00810 agerr(AGWARN, 00811 "No or improper image=\"%s\" for node \"%s\"\n", 00812 (sfile ? sfile : "<nil>"), agnameof(n)); 00813 imagesize.x = imagesize.y = 0; 00814 } else { 00815 GD_has_images(agraphof(n)) = TRUE; 00816 imagesize.x += 2; /* some fixed padding */ 00817 imagesize.y += 2; 00818 } 00819 } 00820 00821 /* initialize node bb to labelsize */ 00822 bb.x = MAX(dimen.x, imagesize.x); 00823 bb.y = MAX(dimen.y, imagesize.y); 00824 00825 /* I don't know how to distort or skew ellipses in postscript */ 00826 /* Convert request to a polygon with a large number of sides */ 00827 if ((sides <= 2) && ((distortion != 0.) || (skew != 0.))) { 00828 sides = 120; 00829 } 00830 00831 /* extra sizing depends on if label is centered vertically */ 00832 p = agget(n, "labelloc"); 00833 if (p && (p[0] == 't' || p[0] == 'b')) 00834 ND_label(n)->valign = p[0]; 00835 else 00836 ND_label(n)->valign = 'c'; 00837 00838 isBox = (sides == 4 && (ROUND(orientation) % 90) == 0 00839 && distortion == 0. && skew == 0.); 00840 if (isBox) { 00841 /* for regular boxes the fit should be exact */ 00842 } else { 00843 /* for all other shapes, compute a smallest ellipse 00844 * containing bb centered on the origin, and then pad for that. 00845 * We assume the ellipse is defined by a scaling up of bb. 00846 */ 00847 temp = bb.y * SQRT2; 00848 if (height > temp && ND_label(n)->valign == 'c') { 00849 /* if there is height to spare 00850 * and the label is centered vertically 00851 * then just pad x in proportion to the spare height */ 00852 bb.x *= sqrt(1. / (1. - SQR(bb.y / height))); 00853 } else { 00854 bb.x *= SQRT2; 00855 bb.y = temp; 00856 } 00857 #if 1 00858 if (sides > 2) { 00859 temp = cos(M_PI / sides); 00860 bb.x /= temp; 00861 bb.y /= temp; 00862 /* FIXME - for odd-sided polygons, e.g. triangles, there 00863 would be a better fit with some vertical adjustment of the shape */ 00864 } 00865 #endif 00866 } 00867 00868 /* at this point, bb is the minimum size of node that can hold the label */ 00869 min_bb = bb; 00870 00871 /* increase node size to width/height if needed */ 00872 if (mapbool(late_string(n, N_fixed, "false"))) { 00873 if ((width < bb.x) || (height < bb.y)) 00874 agerr(AGWARN, 00875 "node '%s', graph '%s' size too small for label\n", 00876 agnameof(n), agnameof(agraphof(n))); 00877 bb.x = width; 00878 bb.y = height; 00879 } else { 00880 bb.x = width = MAX(width, bb.x); 00881 bb.y = height = MAX(height, bb.y); 00882 } 00883 00884 /* If regular, make dimensions the same. 00885 * Need this to guarantee final node size is regular. 00886 */ 00887 if (regular) { 00888 width = height = bb.x = bb.y = MAX(bb.x, bb.y); 00889 } 00890 00891 /* Compute space available for label. Provides the justification borders */ 00892 if (!mapbool(late_string(n, N_nojustify, "false"))) { 00893 if (isBox) { 00894 ND_label(n)->space.x = MAX(dimen.x,bb.x) - spacex; 00895 } 00896 else if (dimen.y < bb.y) { 00897 temp = bb.x * sqrt(1.0 - SQR(dimen.y) / SQR(bb.y)); 00898 ND_label(n)->space.x = MAX(dimen.x,temp) - spacex; 00899 } 00900 else 00901 ND_label(n)->space.x = dimen.x - spacex; 00902 } else { 00903 ND_label(n)->space.x = dimen.x - spacex; 00904 } 00905 00906 00907 temp = bb.y - min_bb.y; 00908 if (dimen.y < imagesize.y) 00909 temp += imagesize.y - dimen.y; 00910 ND_label(n)->space.y = dimen.y + temp; 00911 00912 outp = peripheries; 00913 if (peripheries < 1) 00914 outp = 1; 00915 if (sides < 3) { /* ellipses */ 00916 sides = 2; 00917 vertices = N_NEW(outp * sides, pointf); 00918 P.x = bb.x / 2.; 00919 P.y = bb.y / 2.; 00920 vertices[0].x = -P.x; 00921 vertices[0].y = -P.y; 00922 vertices[1] = P; 00923 if (peripheries > 1) { 00924 for (j = 1, i = 2; j < peripheries; j++) { 00925 P.x += GAP; 00926 P.y += GAP; 00927 vertices[i].x = -P.x; 00928 vertices[i].y = -P.y; 00929 i++; 00930 vertices[i].x = P.x; 00931 vertices[i].y = P.y; 00932 i++; 00933 } 00934 bb.x = 2. * P.x; 00935 bb.y = 2. * P.y; 00936 } 00937 } else { 00938 vertices = N_NEW(outp * sides, pointf); 00939 sectorangle = 2. * M_PI / sides; 00940 sidelength = sin(sectorangle / 2.); 00941 skewdist = hypot(fabs(distortion) + fabs(skew), 1.); 00942 gdistortion = distortion * SQRT2 / cos(sectorangle / 2.); 00943 gskew = skew / 2.; 00944 angle = (sectorangle - M_PI) / 2.; 00945 sincos(angle, &sinx, &cosx); 00946 R.x = .5 * cosx; 00947 R.y = .5 * sinx; 00948 xmax = ymax = 0.; 00949 angle += (M_PI - sectorangle) / 2.; 00950 for (i = 0; i < sides; i++) { 00951 00952 /*next regular vertex */ 00953 angle += sectorangle; 00954 sincos(angle, &sinx, &cosx); 00955 R.x += sidelength * cosx; 00956 R.y += sidelength * sinx; 00957 00958 /*distort and skew */ 00959 P.x = R.x * (skewdist + R.y * gdistortion) + R.y * gskew; 00960 P.y = R.y; 00961 00962 /*orient P.x,P.y */ 00963 alpha = RADIANS(orientation) + atan2(P.y, P.x); 00964 sincos(alpha, &sinx, &cosx); 00965 P.x = P.y = hypot(P.x, P.y); 00966 P.x *= cosx; 00967 P.y *= sinx; 00968 00969 /*scale for label */ 00970 P.x *= bb.x; 00971 P.y *= bb.y; 00972 00973 /*find max for bounding box */ 00974 xmax = MAX(fabs(P.x), xmax); 00975 ymax = MAX(fabs(P.y), ymax); 00976 00977 /* store result in array of points */ 00978 vertices[i] = P; 00979 if (isBox) { /* enforce exact symmetry of box */ 00980 vertices[1].x = -P.x; 00981 vertices[1].y = P.y; 00982 vertices[2].x = -P.x; 00983 vertices[2].y = -P.y; 00984 vertices[3].x = P.x; 00985 vertices[3].y = -P.y; 00986 break; 00987 } 00988 } 00989 00990 /* apply minimum dimensions */ 00991 xmax *= 2.; 00992 ymax *= 2.; 00993 bb.x = MAX(width, xmax); 00994 bb.y = MAX(height, ymax); 00995 scalex = bb.x / xmax; 00996 scaley = bb.y / ymax; 00997 00998 for (i = 0; i < sides; i++) { 00999 P = vertices[i]; 01000 P.x *= scalex; 01001 P.y *= scaley; 01002 vertices[i] = P; 01003 } 01004 01005 if (peripheries > 1) { 01006 Q = vertices[(sides - 1)]; 01007 R = vertices[0]; 01008 beta = atan2(R.y - Q.y, R.x - Q.x); 01009 for (i = 0; i < sides; i++) { 01010 01011 /*for each vertex find the bisector */ 01012 P = Q; 01013 Q = R; 01014 R = vertices[(i + 1) % sides]; 01015 alpha = beta; 01016 beta = atan2(R.y - Q.y, R.x - Q.x); 01017 gamma = (alpha + M_PI - beta) / 2.; 01018 01019 /*find distance along bisector to */ 01020 /*intersection of next periphery */ 01021 temp = GAP / sin(gamma); 01022 01023 /*convert this distance to x and y */ 01024 sincos((alpha - gamma), &sinx, &cosx); 01025 sinx *= temp; 01026 cosx *= temp; 01027 01028 /*save the vertices of all the */ 01029 /*peripheries at this base vertex */ 01030 for (j = 1; j < peripheries; j++) { 01031 Q.x += cosx; 01032 Q.y += sinx; 01033 vertices[i + j * sides] = Q; 01034 } 01035 } 01036 for (i = 0; i < sides; i++) { 01037 P = vertices[i + (peripheries - 1) * sides]; 01038 bb.x = MAX(2. * fabs(P.x), bb.x); 01039 bb.y = MAX(2. * fabs(P.y), bb.y); 01040 } 01041 } 01042 } 01043 poly->regular = regular; 01044 poly->peripheries = peripheries; 01045 poly->sides = sides; 01046 poly->orientation = orientation; 01047 poly->skew = skew; 01048 poly->distortion = distortion; 01049 poly->vertices = vertices; 01050 01051 ND_width(n) = PS2INCH(bb.x); 01052 ND_height(n) = PS2INCH(bb.y); 01053 ND_shape_info(n) = (void *) poly; 01054 } 01055 01056 static void poly_free(node_t * n) 01057 { 01058 polygon_t *p = ND_shape_info(n); 01059 01060 if (p) { 01061 free(p->vertices); 01062 free(p); 01063 } 01064 } 01065 01066 #define GET_PORT_BOX(n,e) ((n) == (e)->head ? ED_head_port(e).bp : ED_tail_port(e).bp) 01067 01068 /* poly_inside: 01069 * Return true if point p is inside polygonal shape of node inside_context->s.n. 01070 * Calculations are done using unrotated node shape. Thus, if p is in a rotated 01071 * coordinate system, it is reset as P in the unrotated coordinate system. Similarly, 01072 * the ND_rw, ND_lw and ND_ht values are rotated if the graph is flipped. 01073 */ 01074 static boolean poly_inside(inside_t * inside_context, pointf p) 01075 { 01076 static node_t *lastn; /* last node argument */ 01077 static polygon_t *poly; 01078 static int last, outp, sides; 01079 static pointf O; /* point (0,0) */ 01080 static pointf *vertex; 01081 static double xsize, ysize, scalex, scaley, box_URx, box_URy; 01082 01083 int i, i1, j, s; 01084 pointf P, Q, R; 01085 boxf *bp = inside_context->s.bp; 01086 node_t *n = inside_context->s.n; 01087 01088 P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n))); 01089 01090 /* Quick test if port rectangle is target */ 01091 if (bp) { 01092 boxf bbox = *bp; 01093 return INSIDE(P, bbox); 01094 } 01095 01096 if (n != lastn) { 01097 poly = (polygon_t *) ND_shape_info(n); 01098 vertex = poly->vertices; 01099 sides = poly->sides; 01100 01101 /* get point and node size adjusted for rankdir=LR */ 01102 if (GD_flip(agraphof(n))) { 01103 ysize = ND_lw(n) + ND_rw(n); 01104 xsize = ND_ht(n); 01105 } else { 01106 xsize = ND_lw(n) + ND_rw(n); 01107 ysize = ND_ht(n); 01108 } 01109 01110 /* scale */ 01111 if (xsize == 0.0) 01112 xsize = 1.0; 01113 if (ysize == 0.0) 01114 ysize = 1.0; 01115 scalex = POINTS(ND_width(n)) / xsize; 01116 scaley = POINTS(ND_height(n)) / ysize; 01117 box_URx = POINTS(ND_width(n)) / 2.0; 01118 box_URy = POINTS(ND_height(n)) / 2.0; 01119 01120 /* index to outer-periphery */ 01121 outp = (poly->peripheries - 1) * sides; 01122 if (outp < 0) 01123 outp = 0; 01124 lastn = n; 01125 } 01126 01127 /* scale */ 01128 P.x *= scalex; 01129 P.y *= scaley; 01130 01131 /* inside bounding box? */ 01132 if ((fabs(P.x) > box_URx) || (fabs(P.y) > box_URy)) 01133 return FALSE; 01134 01135 /* ellipses */ 01136 if (sides <= 2) 01137 return (hypot(P.x / box_URx, P.y / box_URy) < 1.); 01138 01139 /* use fast test in case we are converging on a segment */ 01140 i = last % sides; /* in case last left over from larger polygon */ 01141 i1 = (i + 1) % sides; 01142 Q = vertex[i + outp]; 01143 R = vertex[i1 + outp]; 01144 if (!(same_side(P, O, Q, R))) /* false if outside the segment's face */ 01145 return FALSE; 01146 /* else inside the segment face... */ 01147 if ((s = same_side(P, Q, R, O)) && (same_side(P, R, O, Q))) /* true if between the segment's sides */ 01148 return TRUE; 01149 /* else maybe in another segment */ 01150 for (j = 1; j < sides; j++) { /* iterate over remaining segments */ 01151 if (s) { /* clockwise */ 01152 i = i1; 01153 i1 = (i + 1) % sides; 01154 } else { /* counter clockwise */ 01155 i1 = i; 01156 i = (i + sides - 1) % sides; 01157 } 01158 if (!(same_side(P, O, vertex[i + outp], vertex[i1 + outp]))) { /* false if outside any other segment's face */ 01159 last = i; 01160 return FALSE; 01161 } 01162 } 01163 /* inside all segments' faces */ 01164 last = i; /* in case next edge is to same side */ 01165 return TRUE; 01166 } 01167 01168 /* poly_path: 01169 * Generate box path from port to border. 01170 * Store boxes in rv and number of boxes in kptr. 01171 * side gives preferred side of bounding box for last node. 01172 * Return actual side. Returning 0 indicates nothing done. 01173 */ 01174 static int poly_path(node_t * n, port * p, int side, boxf rv[], int *kptr) 01175 { 01176 side = 0; 01177 01178 if (ND_label(n)->html && ND_has_port(n)) { 01179 side = html_path(n, p, side, rv, kptr); 01180 } 01181 return side; 01182 } 01183 01184 /* invflip_side: 01185 */ 01186 static int invflip_side(int side, int rankdir) 01187 { 01188 switch (rankdir) { 01189 case RANKDIR_TB: 01190 break; 01191 case RANKDIR_BT: 01192 switch (side) { 01193 case TOP: 01194 side = BOTTOM; 01195 break; 01196 case BOTTOM: 01197 side = TOP; 01198 break; 01199 default: 01200 break; 01201 } 01202 break; 01203 case RANKDIR_LR: 01204 switch (side) { 01205 case TOP: 01206 side = RIGHT; 01207 break; 01208 case BOTTOM: 01209 side = LEFT; 01210 break; 01211 case LEFT: 01212 side = TOP; 01213 break; 01214 case RIGHT: 01215 side = BOTTOM; 01216 break; 01217 } 01218 break; 01219 case RANKDIR_RL: 01220 switch (side) { 01221 case TOP: 01222 side = RIGHT; 01223 break; 01224 case BOTTOM: 01225 side = LEFT; 01226 break; 01227 case LEFT: 01228 side = BOTTOM; 01229 break; 01230 case RIGHT: 01231 side = TOP; 01232 break; 01233 } 01234 break; 01235 } 01236 return side; 01237 } 01238 01239 /* invflip_angle: 01240 */ 01241 static double invflip_angle(double angle, int rankdir) 01242 { 01243 switch (rankdir) { 01244 case RANKDIR_TB: 01245 break; 01246 case RANKDIR_BT: 01247 angle *= -1; 01248 break; 01249 case RANKDIR_LR: 01250 angle -= M_PI * 0.5; 01251 break; 01252 case RANKDIR_RL: 01253 if (angle == M_PI) 01254 angle = -0.5 * M_PI; 01255 else if (angle == M_PI * 0.75) 01256 angle = -0.25 * M_PI; 01257 else if (angle == M_PI * 0.5) 01258 angle = 0; 01259 else if (angle == M_PI * 0.25) 01260 angle = angle; 01261 else if (angle == 0) 01262 angle = M_PI * 0.5; 01263 else if (angle == M_PI * -0.25) 01264 angle = M_PI * 0.75; 01265 else if (angle == M_PI * -0.5) 01266 angle = M_PI; 01267 else if (angle == M_PI * -0.75) 01268 angle = angle; 01269 break; 01270 } 01271 return angle; 01272 } 01273 01274 /* compassPoint: 01275 * Compute compass points for non-trivial shapes. 01276 * It finds where the ray ((0,0),(x,y)) hits the boundary and 01277 * returns it. 01278 * Assumes ictxt and ictxt->n are non-NULL. 01279 * 01280 * bezier_clip uses the shape's _inside function, which assumes the input 01281 * point is in the rotated coordinate system (as determined by rankdir), so 01282 * it rotates the point counterclockwise based on rankdir to get the node's 01283 * coordinate system. 01284 * To handle this, if rankdir is set, we rotate (x,y) clockwise, and then 01285 * rotate the answer counterclockwise. 01286 */ 01287 static pointf compassPoint(inside_t * ictxt, double y, double x) 01288 { 01289 pointf curve[4]; /* bezier control points for a straight line */ 01290 node_t *n = ictxt->s.n; 01291 graph_t* g = agraphof(n); 01292 int rd = GD_rankdir(g); 01293 pointf p; 01294 01295 p.x = x; 01296 p.y = y; 01297 if (rd) 01298 p = cwrotatepf(p, 90 * rd); 01299 01300 curve[0].x = curve[0].y = 0; 01301 curve[1] = curve[0]; 01302 curve[3] = curve[2] = p; 01303 01304 bezier_clip(ictxt, ND_shape(n)->fns->insidefn, curve, 1); 01305 01306 if (rd) 01307 curve[0] = ccwrotatepf(curve[0], 90 * rd); 01308 return curve[0]; 01309 } 01310 01311 /* compassPort: 01312 * Attach a compass point to a port pp, and fill in remaining fields. 01313 * n is the corresponding node; bp is the bounding box of the port. 01314 * compass is the compass point 01315 * Return 1 if unrecognized compass point, in which case we 01316 * use the center. 01317 * 01318 * This function also finishes initializing the port structure, 01319 * even if no compass point is involved. 01320 * The sides value gives the set of sides shared by the port. This 01321 * is used with a compass point to indicate if the port is exposed, to 01322 * set the port's side value. 01323 * 01324 * If ictxt is NULL, we are working with a simple rectangular shape (node or 01325 * port of record of HTML label), so compass points are trivial. If ictxt is 01326 * not NULL, it provides shape information so that the compass point can be 01327 * calculated based on the shape. 01328 * 01329 * The code assumes the node has its unrotated shape to find the points, 01330 * angles, etc. At the end, the parameters are adjusted to take into account 01331 * the rankdir attribute. In particular, the first if-else statement flips 01332 * the already adjusted ND_ht, ND_lw and ND_rw back to non-flipped values. 01333 * 01334 */ 01335 static int 01336 compassPort(node_t * n, boxf * bp, port * pp, char *compass, int sides, 01337 inside_t * ictxt) 01338 { 01339 boxf b; 01340 pointf p, ctr; 01341 int rv = 0; 01342 double theta = 0.0; 01343 boolean constrain = FALSE; 01344 boolean dyna = FALSE; 01345 int side = 0; 01346 boolean clip = TRUE; 01347 boolean defined; 01348 double maxv; /* sufficiently large value outside of range of node */ 01349 01350 if (bp) { 01351 b = *bp; 01352 p = pointfof((b.LL.x + b.UR.x) / 2, (b.LL.y + b.UR.y) / 2); 01353 defined = TRUE; 01354 } else { 01355 p.x = p.y = 0.; 01356 if (GD_flip(agraphof(n))) { 01357 b.UR.x = ND_ht(n) / 2.; 01358 b.LL.x = -b.UR.x; 01359 b.UR.y = ND_lw(n); 01360 b.LL.y = -b.UR.y; 01361 } else { 01362 b.UR.y = ND_ht(n) / 2.; 01363 b.LL.y = -b.UR.y; 01364 b.UR.x = ND_lw(n); 01365 b.LL.x = -b.UR.x; 01366 } 01367 defined = FALSE; 01368 } 01369 maxv = MAX(b.UR.x,b.UR.y); 01370 maxv *= 4.0; 01371 ctr = p; 01372 if (compass && *compass) { 01373 switch (*compass++) { 01374 case 'e': 01375 if (*compass) 01376 rv = 1; 01377 else { 01378 if (ictxt) 01379 p = compassPoint(ictxt, ctr.y, maxv); 01380 else 01381 p.x = b.UR.x; 01382 theta = 0.0; 01383 constrain = TRUE; 01384 defined = TRUE; 01385 clip = FALSE; 01386 side = sides & RIGHT; 01387 } 01388 break; 01389 case 's': 01390 p.y = b.LL.y; 01391 constrain = TRUE; 01392 clip = FALSE; 01393 switch (*compass) { 01394 case '\0': 01395 theta = -M_PI * 0.5; 01396 defined = TRUE; 01397 if (ictxt) 01398 p = compassPoint(ictxt, -maxv, ctr.x); 01399 else 01400 p.x = ctr.x; 01401 side = sides & BOTTOM; 01402 break; 01403 case 'e': 01404 theta = -M_PI * 0.25; 01405 defined = TRUE; 01406 if (ictxt) 01407 p = compassPoint(ictxt, -maxv, maxv); 01408 else 01409 p.x = b.UR.x; 01410 side = sides & (BOTTOM | RIGHT); 01411 break; 01412 case 'w': 01413 theta = -M_PI * 0.75; 01414 defined = TRUE; 01415 if (ictxt) 01416 p = compassPoint(ictxt, -maxv, -maxv); 01417 else 01418 p.x = b.LL.x; 01419 side = sides & (BOTTOM | LEFT); 01420 break; 01421 default: 01422 p.y = ctr.y; 01423 constrain = FALSE; 01424 clip = TRUE; 01425 rv = 1; 01426 break; 01427 } 01428 break; 01429 case 'w': 01430 if (*compass) 01431 rv = 1; 01432 else { 01433 if (ictxt) 01434 p = compassPoint(ictxt, ctr.y, -maxv); 01435 else 01436 p.x = b.LL.x; 01437 theta = M_PI; 01438 constrain = TRUE; 01439 defined = TRUE; 01440 clip = FALSE; 01441 side = sides & LEFT; 01442 } 01443 break; 01444 case 'n': 01445 p.y = b.UR.y; 01446 constrain = TRUE; 01447 clip = FALSE; 01448 switch (*compass) { 01449 case '\0': 01450 defined = TRUE; 01451 theta = M_PI * 0.5; 01452 if (ictxt) 01453 p = compassPoint(ictxt, maxv, ctr.x); 01454 else 01455 p.x = ctr.x; 01456 side = sides & TOP; 01457 break; 01458 case 'e': 01459 defined = TRUE; 01460 theta = M_PI * 0.25; 01461 if (ictxt) 01462 p = compassPoint(ictxt, maxv, maxv); 01463 else 01464 p.x = b.UR.x; 01465 side = sides & (TOP | RIGHT); 01466 break; 01467 case 'w': 01468 defined = TRUE; 01469 theta = M_PI * 0.75; 01470 if (ictxt) 01471 p = compassPoint(ictxt, maxv, -maxv); 01472 else 01473 p.x = b.LL.x; 01474 side = sides & (TOP | LEFT); 01475 break; 01476 default: 01477 p.y = ctr.y; 01478 constrain = FALSE; 01479 clip = TRUE; 01480 rv = 1; 01481 break; 01482 } 01483 break; 01484 case '_': 01485 dyna = TRUE; 01486 side = sides; 01487 break; 01488 case 'c': 01489 break; 01490 default: 01491 rv = 1; 01492 break; 01493 } 01494 } 01495 p = cwrotatepf(p, 90 * GD_rankdir(agraphof(n))); 01496 if (dyna) 01497 pp->side = side; 01498 else 01499 pp->side = invflip_side(side, GD_rankdir(agraphof(n))); 01500 pp->bp = bp; 01501 PF2P(p, pp->p); 01502 pp->theta = invflip_angle(theta, GD_rankdir(agraphof(n))); 01503 if ((p.x == 0) && (p.y == 0)) 01504 pp->order = MC_SCALE / 2; 01505 else { 01506 /* compute angle with 0 at north pole, increasing CCW */ 01507 double angle = atan2(p.y, p.x) + 1.5 * M_PI; 01508 if (angle >= 2 * M_PI) 01509 angle -= 2 * M_PI; 01510 pp->order = (int) ((MC_SCALE * angle) / (2 * M_PI)); 01511 } 01512 pp->constrained = constrain; 01513 pp->defined = defined; 01514 pp->clip = clip; 01515 pp->dyna = dyna; 01516 return rv; 01517 } 01518 01519 static port poly_port(node_t * n, char *portname, char *compass) 01520 { 01521 port rv; 01522 boxf *bp; 01523 int sides; /* bitmap of which sides the port lies along */ 01524 01525 if (portname[0] == '\0') 01526 return Center; 01527 01528 if (compass == NULL) 01529 compass = "_"; 01530 sides = BOTTOM | RIGHT | TOP | LEFT; 01531 if ((ND_label(n)->html) && (bp = html_port(n, portname, &sides))) { 01532 if (compassPort(n, bp, &rv, compass, sides, NULL)) { 01533 agerr(AGWARN, 01534 "node %s, port %s, unrecognized compass point '%s' - ignored\n", 01535 agnameof(n), portname, compass); 01536 } 01537 } else { 01538 inside_t *ictxtp; 01539 inside_t ictxt; 01540 01541 if (IS_BOX(n)) 01542 ictxtp = NULL; 01543 else { 01544 ictxt.s.n = n; 01545 ictxt.s.bp = NULL; 01546 ictxtp = &ictxt; 01547 } 01548 if (compassPort(n, NULL, &rv, portname, sides, ictxtp)) 01549 unrecognized(n, portname); 01550 } 01551 01552 return rv; 01553 } 01554 01555 /* generic polygon gencode routine */ 01556 static void poly_gencode(GVJ_t * job, node_t * n) 01557 { 01558 obj_state_t *obj = job->obj; 01559 polygon_t *poly; 01560 double xsize, ysize; 01561 int i, j, peripheries, sides, style; 01562 pointf P, *vertices; 01563 static pointf *AF; 01564 static int A_size; 01565 boolean filled; 01566 boolean usershape_p; 01567 boolean pfilled; /* true if fill not handled by user shape */ 01568 char *color, *name; 01569 int doMap = (obj->url || obj->explicit_tooltip); 01570 char* fillcolor; 01571 char* clrs[2]; 01572 01573 if (doMap && !(job->flags & EMIT_CLUSTERS_LAST)) 01574 gvrender_begin_anchor(job, 01575 obj->url, obj->tooltip, obj->target, 01576 obj->id); 01577 01578 poly = (polygon_t *) ND_shape_info(n); 01579 vertices = poly->vertices; 01580 sides = poly->sides; 01581 peripheries = poly->peripheries; 01582 if (A_size < sides) { 01583 A_size = sides + 5; 01584 AF = ALLOC(A_size, AF, pointf); 01585 } 01586 01587 /* nominal label position in the center of the node */ 01588 ND_label(n)->pos = ND_coord(n); 01589 01590 xsize = (ND_lw(n) + ND_rw(n)) / POINTS(ND_width(n)); 01591 ysize = ND_ht(n) / POINTS(ND_height(n)); 01592 01593 style = stylenode(job, n); 01594 clrs[0] = NULL; 01595 01596 if (ND_gui_state(n) & GUI_STATE_ACTIVE) { 01597 color = late_nnstring(n, N_activepencolor, DEFAULT_ACTIVEPENCOLOR); 01598 gvrender_set_pencolor(job, color); 01599 color = 01600 late_nnstring(n, N_activefillcolor, DEFAULT_ACTIVEFILLCOLOR); 01601 gvrender_set_fillcolor(job, color); 01602 filled = FILL; 01603 } else if (ND_gui_state(n) & GUI_STATE_SELECTED) { 01604 color = 01605 late_nnstring(n, N_selectedpencolor, DEFAULT_SELECTEDPENCOLOR); 01606 gvrender_set_pencolor(job, color); 01607 color = 01608 late_nnstring(n, N_selectedfillcolor, 01609 DEFAULT_SELECTEDFILLCOLOR); 01610 gvrender_set_fillcolor(job, color); 01611 filled = FILL; 01612 } else if (ND_gui_state(n) & GUI_STATE_DELETED) { 01613 color = 01614 late_nnstring(n, N_deletedpencolor, DEFAULT_DELETEDPENCOLOR); 01615 gvrender_set_pencolor(job, color); 01616 color = 01617 late_nnstring(n, N_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR); 01618 gvrender_set_fillcolor(job, color); 01619 filled = FILL; 01620 } else if (ND_gui_state(n) & GUI_STATE_VISITED) { 01621 color = 01622 late_nnstring(n, N_visitedpencolor, DEFAULT_VISITEDPENCOLOR); 01623 gvrender_set_pencolor(job, color); 01624 color = 01625 late_nnstring(n, N_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR); 01626 gvrender_set_fillcolor(job, color); 01627 filled = FILL; 01628 } else { 01629 if (style & FILLED) { 01630 fillcolor = findFill (n); 01631 if (findStopColor (fillcolor, clrs)) { 01632 gvrender_set_fillcolor(job, clrs[0]); 01633 if (clrs[1]) 01634 gvrender_set_gradient_vals(job,clrs[1],late_int(n,N_gradientangle,0,0)); 01635 else 01636 gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(n,N_gradientangle,0,0)); 01637 if (style & RADIAL) 01638 filled = RGRADIENT; 01639 else 01640 filled = GRADIENT; 01641 } 01642 else { 01643 gvrender_set_fillcolor(job, fillcolor); 01644 filled = FILL; 01645 } 01646 } 01647 else { 01648 filled = FALSE; 01649 } 01650 pencolor(job, n); /* emit pen color */ 01651 } 01652 01653 pfilled = !ND_shape(n)->usershape || streq(ND_shape(n)->name, "custom"); 01654 01655 /* if no boundary but filled, set boundary color to transparent */ 01656 if ((peripheries == 0) && filled && pfilled) { 01657 peripheries = 1; 01658 gvrender_set_pencolor(job, "transparent"); 01659 } 01660 usershape_p = FALSE; 01661 if (ND_shape(n)->usershape) { 01662 name = ND_shape(n)->name; 01663 if (streq(name, "custom")) 01664 name = agget(n, "shapefile"); 01665 usershape_p = TRUE; 01666 } else if ((name = agget(n, "image"))) { 01667 usershape_p = TRUE; 01668 } 01669 if (usershape_p) { 01670 /* get coords of innermost periphery */ 01671 for (i = 0; i < sides; i++) { 01672 P = vertices[i]; 01673 AF[i].x = P.x * xsize + ND_coord(n).x; 01674 AF[i].y = P.y * ysize + ND_coord(n).y; 01675 } 01676 /* lay down fill first */ 01677 if (filled && pfilled) { 01678 if (sides <= 2) { 01679 gvrender_ellipse(job, AF, sides, filled); 01680 if (style & DIAGONALS) { 01681 Mcircle_hack(job, n); 01682 } 01683 } else if (style & (ROUNDED | DIAGONALS)) { 01684 round_corners(job, AF, sides, style, filled); 01685 } else { 01686 gvrender_polygon(job, AF, sides, filled); 01687 } 01688 } 01689 gvrender_usershape(job, name, AF, sides, filled, 01690 late_string(n, N_imagescale, "false")); 01691 filled = FALSE; /* with user shapes, we have done the fill if needed */ 01692 } 01693 01694 for (j = 0; j < peripheries; j++) { 01695 for (i = 0; i < sides; i++) { 01696 P = vertices[i + j * sides]; 01697 AF[i].x = P.x * xsize + ND_coord(n).x; 01698 AF[i].y = P.y * ysize + ND_coord(n).y; 01699 } 01700 if (sides <= 2) { 01701 gvrender_ellipse(job, AF, sides, filled); 01702 if (style & DIAGONALS) { 01703 Mcircle_hack(job, n); 01704 } 01705 } else if (SPECIAL_CORNERS(style)) { 01706 round_corners(job, AF, sides, style, filled); 01707 } else { 01708 gvrender_polygon(job, AF, sides, filled); 01709 } 01710 /* fill innermost periphery only */ 01711 filled = FALSE; 01712 } 01713 free (clrs[0]); 01714 01715 emit_label(job, EMIT_NLABEL, ND_label(n)); 01716 if (doMap) { 01717 if (job->flags & EMIT_CLUSTERS_LAST) 01718 gvrender_begin_anchor(job, 01719 obj->url, obj->tooltip, obj->target, 01720 obj->id); 01721 gvrender_end_anchor(job); 01722 } 01723 } 01724 01725 /*=======================end poly======================================*/ 01726 01727 /*===============================point start========================*/ 01728 01729 /* point_init: 01730 * shorthand for shape=circle, style=filled, width=0.05, label="" 01731 */ 01732 static void point_init(node_t * n) 01733 { 01734 polygon_t *poly = NEW(polygon_t); 01735 int sides, outp, peripheries = ND_shape(n)->polygon->peripheries; 01736 double sz; 01737 pointf P, *vertices; 01738 int i, j; 01739 double w, h; 01740 01741 /* set width and height, and make them equal 01742 * if user has set weight or height, use it. 01743 * if both are set, use smallest. 01744 * if neither, use default 01745 */ 01746 w = late_double(n, N_width, MAXDOUBLE, 0.0); 01747 h = late_double(n, N_height, MAXDOUBLE, 0.0); 01748 w = MIN(w, h); 01749 if ((w == MAXDOUBLE) && (h == MAXDOUBLE)) /* neither defined */ 01750 ND_width(n) = ND_height(n) = DEF_POINT; 01751 else { 01752 w = MIN(w, h); 01753 /* If w == 0, use it; otherwise, make w no less than MIN_POINT due 01754 * to the restrictions mentioned above. 01755 */ 01756 if (w > 0.0) 01757 w = MAX(w,MIN_POINT); 01758 ND_width(n) = ND_height(n) = w; 01759 } 01760 01761 sz = ND_width(n) * POINTS_PER_INCH; 01762 peripheries = late_int(n, N_peripheries, peripheries, 0); 01763 if (peripheries < 1) 01764 outp = 1; 01765 else 01766 outp = peripheries; 01767 sides = 2; 01768 vertices = N_NEW(outp * sides, pointf); 01769 P.y = P.x = sz / 2.; 01770 vertices[0].x = -P.x; 01771 vertices[0].y = -P.y; 01772 vertices[1] = P; 01773 if (peripheries > 1) { 01774 for (j = 1, i = 2; j < peripheries; j++) { 01775 P.x += GAP; 01776 P.y += GAP; 01777 vertices[i].x = -P.x; 01778 vertices[i].y = -P.y; 01779 i++; 01780 vertices[i].x = P.x; 01781 vertices[i].y = P.y; 01782 i++; 01783 } 01784 sz = 2. * P.x; 01785 } 01786 poly->regular = 1; 01787 poly->peripheries = peripheries; 01788 poly->sides = 2; 01789 poly->orientation = 0; 01790 poly->skew = 0; 01791 poly->distortion = 0; 01792 poly->vertices = vertices; 01793 01794 ND_height(n) = ND_width(n) = PS2INCH(sz); 01795 ND_shape_info(n) = (void *) poly; 01796 } 01797 01798 static boolean point_inside(inside_t * inside_context, pointf p) 01799 { 01800 static node_t *lastn; /* last node argument */ 01801 static double radius; 01802 pointf P; 01803 node_t *n = inside_context->s.n; 01804 01805 P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n))); 01806 01807 if (n != lastn) { 01808 int outp; 01809 polygon_t *poly = (polygon_t *) ND_shape_info(n); 01810 01811 /* index to outer-periphery */ 01812 outp = 2 * (poly->peripheries - 1); 01813 if (outp < 0) 01814 outp = 0; 01815 01816 radius = poly->vertices[outp + 1].x; 01817 lastn = n; 01818 } 01819 01820 /* inside bounding box? */ 01821 if ((fabs(P.x) > radius) || (fabs(P.y) > radius)) 01822 return FALSE; 01823 01824 return (hypot(P.x, P.y) <= radius); 01825 } 01826 01827 static void point_gencode(GVJ_t * job, node_t * n) 01828 { 01829 obj_state_t *obj = job->obj; 01830 polygon_t *poly; 01831 int i, j, sides, peripheries, style; 01832 pointf P, *vertices; 01833 static pointf *AF; 01834 static int A_size; 01835 boolean filled; 01836 char *color; 01837 int doMap = (obj->url || obj->explicit_tooltip); 01838 01839 if (doMap && !(job->flags & EMIT_CLUSTERS_LAST)) 01840 gvrender_begin_anchor(job, 01841 obj->url, obj->tooltip, obj->target, 01842 obj->id); 01843 01844 poly = (polygon_t *) ND_shape_info(n); 01845 vertices = poly->vertices; 01846 sides = poly->sides; 01847 peripheries = poly->peripheries; 01848 if (A_size < sides) { 01849 A_size = sides + 2; 01850 AF = ALLOC(A_size, AF, pointf); 01851 } 01852 01853 checkStyle(n, &style); 01854 if (style & INVISIBLE) 01855 gvrender_set_style(job, point_style); 01856 else 01857 gvrender_set_style(job, &point_style[1]); 01858 01859 if (ND_gui_state(n) & GUI_STATE_ACTIVE) { 01860 color = late_nnstring(n, N_activepencolor, DEFAULT_ACTIVEPENCOLOR); 01861 gvrender_set_pencolor(job, color); 01862 color = 01863 late_nnstring(n, N_activefillcolor, DEFAULT_ACTIVEFILLCOLOR); 01864 gvrender_set_fillcolor(job, color); 01865 } else if (ND_gui_state(n) & GUI_STATE_SELECTED) { 01866 color = 01867 late_nnstring(n, N_selectedpencolor, DEFAULT_SELECTEDPENCOLOR); 01868 gvrender_set_pencolor(job, color); 01869 color = 01870 late_nnstring(n, N_selectedfillcolor, 01871 DEFAULT_SELECTEDFILLCOLOR); 01872 gvrender_set_fillcolor(job, color); 01873 } else if (ND_gui_state(n) & GUI_STATE_DELETED) { 01874 color = 01875 late_nnstring(n, N_deletedpencolor, DEFAULT_DELETEDPENCOLOR); 01876 gvrender_set_pencolor(job, color); 01877 color = 01878 late_nnstring(n, N_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR); 01879 gvrender_set_fillcolor(job, color); 01880 } else if (ND_gui_state(n) & GUI_STATE_VISITED) { 01881 color = 01882 late_nnstring(n, N_visitedpencolor, DEFAULT_VISITEDPENCOLOR); 01883 gvrender_set_pencolor(job, color); 01884 color = 01885 late_nnstring(n, N_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR); 01886 gvrender_set_fillcolor(job, color); 01887 } else { 01888 color = findFillDflt(n, "black"); 01889 gvrender_set_fillcolor(job, color); /* emit fill color */ 01890 pencolor(job, n); /* emit pen color */ 01891 } 01892 filled = TRUE; 01893 01894 /* if no boundary but filled, set boundary color to fill color */ 01895 if (peripheries == 0) { 01896 peripheries = 1; 01897 if (color[0]) 01898 gvrender_set_pencolor(job, color); 01899 } 01900 01901 for (j = 0; j < peripheries; j++) { 01902 for (i = 0; i < sides; i++) { 01903 P = vertices[i + j * sides]; 01904 AF[i].x = P.x + ND_coord(n).x; 01905 AF[i].y = P.y + ND_coord(n).y; 01906 } 01907 gvrender_ellipse(job, AF, sides, filled); 01908 /* fill innermost periphery only */ 01909 filled = FALSE; 01910 } 01911 01912 if (doMap) { 01913 if (job->flags & EMIT_CLUSTERS_LAST) 01914 gvrender_begin_anchor(job, 01915 obj->url, obj->tooltip, obj->target, 01916 obj->id); 01917 gvrender_end_anchor(job); 01918 } 01919 } 01920 01921 /* the "record" shape is a rudimentary table formatter */ 01922 01923 #define HASTEXT 1 01924 #define HASPORT 2 01925 #define HASTABLE 4 01926 #define INTEXT 8 01927 #define INPORT 16 01928 01929 #define ISCTRL(c) ((c) == '{' || (c) == '}' || (c) == '|' || (c) == '<' || (c) == '>') 01930 01931 static char *reclblp; 01932 01933 static void free_field(field_t * f) 01934 { 01935 int i; 01936 01937 for (i = 0; i < f->n_flds; i++) { 01938 free_field(f->fld[i]); 01939 } 01940 01941 free(f->id); 01942 free_label(f->lp); 01943 free(f->fld); 01944 free(f); 01945 } 01946 01947 /* parse_error: 01948 * Clean up memory allocated in parse_reclbl, then return NULL 01949 */ 01950 static field_t *parse_error(field_t * rv, char *port) 01951 { 01952 free_field(rv); 01953 if (port) 01954 free(port); 01955 return NULL; 01956 } 01957 01958 static field_t *parse_reclbl(node_t * n, int LR, int flag, char *text) 01959 { 01960 field_t *fp, *rv = NEW(field_t); 01961 char *tsp, *psp=NULL, *hstsp, *hspsp=NULL, *sp; 01962 char *tmpport = NULL; 01963 int maxf, cnt, mode, wflag, ishardspace, fi; 01964 textlabel_t *lbl = ND_label(n); 01965 01966 fp = NULL; 01967 for (maxf = 1, cnt = 0, sp = reclblp; *sp; sp++) { 01968 if (*sp == '\\') { 01969 sp++; 01970 if (*sp 01971 && (*sp == '{' || *sp == '}' || *sp == '|' || *sp == '\\')) 01972 continue; 01973 } 01974 if (*sp == '{') 01975 cnt++; 01976 else if (*sp == '}') 01977 cnt--; 01978 else if (*sp == '|' && cnt == 0) 01979 maxf++; 01980 if (cnt < 0) 01981 break; 01982 } 01983 rv->fld = N_NEW(maxf, field_t *); 01984 rv->LR = LR; 01985 mode = 0; 01986 fi = 0; 01987 hstsp = tsp = text; 01988 wflag = TRUE; 01989 ishardspace = FALSE; 01990 while (wflag) { 01991 switch (*reclblp) { 01992 case '<': 01993 if (mode & (HASTABLE | HASPORT)) 01994 return parse_error(rv, tmpport); 01995 if (lbl->html) 01996 goto dotext; 01997 mode |= (HASPORT | INPORT); 01998 reclblp++; 01999 hspsp = psp = text; 02000 break; 02001 case '>': 02002 if (lbl->html) 02003 goto dotext; 02004 if (!(mode & INPORT)) 02005 return parse_error(rv, tmpport); 02006 if (psp > text + 1 && psp - 1 != hspsp && *(psp - 1) == ' ') 02007 psp--; 02008 *psp = '\000'; 02009 tmpport = strdup(text); 02010 mode &= ~INPORT; 02011 reclblp++; 02012 break; 02013 case '{': 02014 reclblp++; 02015 if (mode != 0 || !*reclblp) 02016 return parse_error(rv, tmpport); 02017 mode = HASTABLE; 02018 if (!(rv->fld[fi++] = parse_reclbl(n, NOT(LR), FALSE, text))) 02019 return parse_error(rv, tmpport); 02020 break; 02021 case '}': 02022 case '|': 02023 case '\000': 02024 if ((!*reclblp && !flag) || (mode & INPORT)) 02025 return parse_error(rv, tmpport); 02026 if (!(mode & HASTABLE)) 02027 fp = rv->fld[fi++] = NEW(field_t); 02028 if (tmpport) { 02029 fp->id = tmpport; 02030 tmpport = NULL; 02031 } 02032 if (!(mode & (HASTEXT | HASTABLE))) 02033 mode |= HASTEXT, *tsp++ = ' '; 02034 if (mode & HASTEXT) { 02035 if (tsp > text + 1 && 02036 tsp - 1 != hstsp && *(tsp - 1) == ' ') 02037 tsp--; 02038 *tsp = '\000'; 02039 fp->lp = 02040 make_label((void *) n, strdup(text), 02041 (lbl->html ? LT_HTML : LT_NONE), 02042 lbl->fontsize, lbl->fontname, 02043 lbl->fontcolor); 02044 fp->LR = TRUE; 02045 hstsp = tsp = text; 02046 } 02047 if (*reclblp) { 02048 if (*reclblp == '}') { 02049 reclblp++; 02050 rv->n_flds = fi; 02051 return rv; 02052 } 02053 mode = 0; 02054 reclblp++; 02055 } else 02056 wflag = FALSE; 02057 break; 02058 case '\\': 02059 if (*(reclblp + 1)) { 02060 if (ISCTRL(*(reclblp + 1))) 02061 reclblp++; 02062 else if ((*(reclblp + 1) == ' ') && !lbl->html) 02063 ishardspace = TRUE, reclblp++; 02064 else { 02065 *tsp++ = '\\'; 02066 mode |= (INTEXT | HASTEXT); 02067 reclblp++; 02068 } 02069 } 02070 /* falling through ... */ 02071 default: 02072 dotext: 02073 if ((mode & HASTABLE) && *reclblp != ' ') 02074 return parse_error(rv, tmpport); 02075 if (!(mode & (INTEXT | INPORT)) && *reclblp != ' ') 02076 mode |= (INTEXT | HASTEXT); 02077 if (mode & INTEXT) { 02078 if (! 02079 (*reclblp == ' ' && !ishardspace && *(tsp - 1) == ' ' 02080 && !lbl->html)) 02081 *tsp++ = *reclblp; 02082 if (ishardspace) 02083 hstsp = tsp - 1; 02084 } else if (mode & INPORT) { 02085 if (!(*reclblp == ' ' && !ishardspace && 02086 (psp == text || *(psp - 1) == ' '))) 02087 *psp++ = *reclblp; 02088 if (ishardspace) 02089 hspsp = psp - 1; 02090 } 02091 reclblp++; 02092 while (*reclblp & 128) 02093 *tsp++ = *reclblp++; 02094 break; 02095 } 02096 } 02097 rv->n_flds = fi; 02098 return rv; 02099 } 02100 02101 static pointf size_reclbl(node_t * n, field_t * f) 02102 { 02103 int i; 02104 char *p; 02105 double marginx, marginy; 02106 pointf d, d0; 02107 pointf dimen; 02108 02109 if (f->lp) { 02110 dimen = f->lp->dimen; 02111 02112 /* minimal whitespace around label */ 02113 if ((dimen.x > 0.0) || (dimen.y > 0.0)) { 02114 /* padding */ 02115 if ((p = agget(n, "margin"))) { 02116 i = sscanf(p, "%lf,%lf", &marginx, &marginy); 02117 if (i > 0) { 02118 dimen.x += 2 * POINTS(marginx); 02119 if (i > 1) 02120 dimen.y += 2 * POINTS(marginy); 02121 else 02122 dimen.y += 2 * POINTS(marginy); 02123 } else 02124 PAD(dimen); 02125 } else 02126 PAD(dimen); 02127 } 02128 d = dimen; 02129 } else { 02130 d.x = d.y = 0; 02131 for (i = 0; i < f->n_flds; i++) { 02132 d0 = size_reclbl(n, f->fld[i]); 02133 if (f->LR) { 02134 d.x += d0.x; 02135 d.y = MAX(d.y, d0.y); 02136 } else { 02137 d.y += d0.y; 02138 d.x = MAX(d.x, d0.x); 02139 } 02140 } 02141 } 02142 f->size = d; 02143 return d; 02144 } 02145 02146 static void resize_reclbl(field_t * f, pointf sz, int nojustify_p) 02147 { 02148 int i, amt; 02149 double inc; 02150 pointf d; 02151 pointf newsz; 02152 field_t *sf; 02153 02154 /* adjust field */ 02155 d.x = sz.x - f->size.x; 02156 d.y = sz.y - f->size.y; 02157 f->size = sz; 02158 02159 /* adjust text area */ 02160 if (f->lp && !nojustify_p) { 02161 f->lp->space.x += d.x; 02162 f->lp->space.y += d.y; 02163 } 02164 02165 /* adjust children */ 02166 if (f->n_flds) { 02167 02168 if (f->LR) 02169 inc = d.x / f->n_flds; 02170 else 02171 inc = d.y / f->n_flds; 02172 for (i = 0; i < f->n_flds; i++) { 02173 sf = f->fld[i]; 02174 amt = ((int) ((i + 1) * inc)) - ((int) (i * inc)); 02175 if (f->LR) 02176 newsz = pointfof(sf->size.x + amt, sz.y); 02177 else 02178 newsz = pointfof(sz.x, sf->size.y + amt); 02179 resize_reclbl(sf, newsz, nojustify_p); 02180 } 02181 } 02182 } 02183 02184 /* pos_reclbl: 02185 * Assign position info for each field. Also, set 02186 * the sides attribute, which indicates which sides of the 02187 * record are accessible to the field. 02188 */ 02189 static void pos_reclbl(field_t * f, pointf ul, int sides) 02190 { 02191 int i, last, mask; 02192 02193 f->sides = sides; 02194 f->b.LL = pointfof(ul.x, ul.y - f->size.y); 02195 f->b.UR = pointfof(ul.x + f->size.x, ul.y); 02196 last = f->n_flds - 1; 02197 for (i = 0; i <= last; i++) { 02198 if (sides) { 02199 if (f->LR) { 02200 if (i == 0) { 02201 if (i == last) 02202 mask = TOP | BOTTOM | RIGHT | LEFT; 02203 else 02204 mask = TOP | BOTTOM | LEFT; 02205 } else if (i == last) 02206 mask = TOP | BOTTOM | RIGHT; 02207 else 02208 mask = TOP | BOTTOM; 02209 } else { 02210 if (i == 0) { 02211 if (i == last) 02212 mask = TOP | BOTTOM | RIGHT | LEFT; 02213 else 02214 mask = TOP | RIGHT | LEFT; 02215 } else if (i == last) 02216 mask = LEFT | BOTTOM | RIGHT; 02217 else 02218 mask = LEFT | RIGHT; 02219 } 02220 } else 02221 mask = 0; 02222 pos_reclbl(f->fld[i], ul, sides & mask); 02223 if (f->LR) 02224 ul.x = ul.x + f->fld[i]->size.x; 02225 else 02226 ul.y = ul.y - f->fld[i]->size.y; 02227 } 02228 } 02229 02230 #ifdef DEBUG 02231 static void indent(int l) 02232 { 02233 int i; 02234 for (i = 0; i < l; i++) 02235 fputs(" ", stderr); 02236 } 02237 02238 static void prbox(boxf b) 02239 { 02240 fprintf(stderr, "((%.5g,%.5g),(%.5g,%.5g))\n", b.LL.x, b.LL.y, b.UR.x, 02241 b.UR.y); 02242 } 02243 02244 static void dumpL(field_t * info, int level) 02245 { 02246 int i; 02247 02248 indent(level); 02249 if (info->n_flds == 0) { 02250 fprintf(stderr, "Label \"%s\" ", info->lp->text); 02251 prbox(info->b); 02252 } else { 02253 fprintf(stderr, "Tbl "); 02254 prbox(info->b); 02255 for (i = 0; i < info->n_flds; i++) { 02256 dumpL(info->fld[i], level + 1); 02257 } 02258 } 02259 } 02260 #endif 02261 02262 /* syntax of labels: foo|bar|baz or foo|(recursive|label)|baz */ 02263 static void record_init(node_t * n) 02264 { 02265 field_t *info; 02266 pointf ul, sz; 02267 int flip, len; 02268 char *textbuf; /* temp buffer for storing labels */ 02269 int sides = BOTTOM | RIGHT | TOP | LEFT; 02270 02271 /* Always use rankdir to determine how records are laid out */ 02272 flip = NOT(GD_realflip(agraphof(n))); 02273 reclblp = ND_label(n)->text; 02274 len = strlen(reclblp); 02275 /* For some forgotten reason, an empty label is parsed into a space, so 02276 * we need at least two bytes in textbuf. 02277 */ 02278 len = MAX(len, 1); 02279 textbuf = N_NEW(len + 1, char); 02280 if (!(info = parse_reclbl(n, flip, TRUE, textbuf))) { 02281 agerr(AGERR, "bad label format %s\n", ND_label(n)->text); 02282 reclblp = "\\N"; 02283 info = parse_reclbl(n, flip, TRUE, textbuf); 02284 } 02285 free(textbuf); 02286 size_reclbl(n, info); 02287 sz.x = POINTS(ND_width(n)); 02288 sz.y = POINTS(ND_height(n)); 02289 if (mapbool(late_string(n, N_fixed, "false"))) { 02290 if ((sz.x < info->size.x) || (sz.y < info->size.y)) { 02291 /* should check that the record really won't fit, e.g., there may be no text. 02292 agerr(AGWARN, "node '%s' size may be too small\n", agnameof(n)); 02293 */ 02294 } 02295 } else { 02296 sz.x = MAX(info->size.x, sz.x); 02297 sz.y = MAX(info->size.y, sz.y); 02298 } 02299 resize_reclbl(info, sz, mapbool(late_string(n, N_nojustify, "false"))); 02300 ul = pointfof(-sz.x / 2., sz.y / 2.); /* FIXME - is this still true: suspected to introduce ronding error - see Kluge below */ 02301 pos_reclbl(info, ul, sides); 02302 ND_width(n) = PS2INCH(info->size.x); 02303 ND_height(n) = PS2INCH(info->size.y + 1); /* Kluge!! +1 to fix rounding diff between layout and rendering 02304 otherwise we can get -1 coords in output */ 02305 ND_shape_info(n) = (void *) info; 02306 } 02307 02308 static void record_free(node_t * n) 02309 { 02310 field_t *p = ND_shape_info(n); 02311 02312 free_field(p); 02313 } 02314 02315 static field_t *map_rec_port(field_t * f, char *str) 02316 { 02317 field_t *rv; 02318 int sub; 02319 02320 if (f->id && (streq(f->id, str))) 02321 rv = f; 02322 else { 02323 rv = NULL; 02324 for (sub = 0; sub < f->n_flds; sub++) 02325 if ((rv = map_rec_port(f->fld[sub], str))) 02326 break; 02327 } 02328 return rv; 02329 } 02330 02331 static port record_port(node_t * n, char *portname, char *compass) 02332 { 02333 field_t *f; 02334 field_t *subf; 02335 port rv; 02336 int sides; /* bitmap of which sides the port lies along */ 02337 02338 if (portname[0] == '\0') 02339 return Center; 02340 sides = BOTTOM | RIGHT | TOP | LEFT; 02341 if (compass == NULL) 02342 compass = "_"; 02343 f = (field_t *) ND_shape_info(n); 02344 if ((subf = map_rec_port(f, portname))) { 02345 if (compassPort(n, &subf->b, &rv, compass, subf->sides, NULL)) { 02346 agerr(AGWARN, 02347 "node %s, port %s, unrecognized compass point '%s' - ignored\n", 02348 agnameof(n), portname, compass); 02349 } 02350 } else if (compassPort(n, &f->b, &rv, portname, sides, NULL)) { 02351 unrecognized(n, portname); 02352 } 02353 02354 return rv; 02355 } 02356 02357 /* record_inside: 02358 * Note that this does not handle Mrecords correctly. It assumes 02359 * everything is a rectangle. 02360 */ 02361 static boolean record_inside(inside_t * inside_context, pointf p) 02362 { 02363 02364 field_t *fld0; 02365 boxf *bp = inside_context->s.bp; 02366 node_t *n = inside_context->s.n; 02367 boxf bbox; 02368 02369 /* convert point to node coordinate system */ 02370 p = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n))); 02371 02372 if (bp == NULL) { 02373 fld0 = (field_t *) ND_shape_info(n); 02374 bbox = fld0->b; 02375 } else 02376 bbox = *bp; 02377 02378 return INSIDE(p, bbox); 02379 } 02380 02381 /* record_path: 02382 * Generate box path from port to border. 02383 * See poly_path for constraints. 02384 */ 02385 static int record_path(node_t * n, port * prt, int side, boxf rv[], 02386 int *kptr) 02387 { 02388 int i, ls, rs; 02389 pointf p; 02390 field_t *info; 02391 02392 if (!prt->defined) 02393 return 0; 02394 p = prt->p; 02395 info = (field_t *) ND_shape_info(n); 02396 02397 for (i = 0; i < info->n_flds; i++) { 02398 if (!GD_flip(agraphof(n))) { 02399 ls = info->fld[i]->b.LL.x; 02400 rs = info->fld[i]->b.UR.x; 02401 } else { 02402 ls = info->fld[i]->b.LL.y; 02403 rs = info->fld[i]->b.UR.y; 02404 } 02405 if (BETWEEN(ls, p.x, rs)) { 02406 /* FIXME: I don't understand this code */ 02407 if (GD_flip(agraphof(n))) { 02408 rv[0] = flip_rec_boxf(info->fld[i]->b, ND_coord(n)); 02409 } else { 02410 rv[0].LL.x = ND_coord(n).x + ls; 02411 rv[0].LL.y = ND_coord(n).y - (ND_ht(n) / 2); 02412 rv[0].UR.x = ND_coord(n).x + rs; 02413 } 02414 rv[0].UR.y = ND_coord(n).y + (ND_ht(n) / 2); 02415 *kptr = 1; 02416 break; 02417 } 02418 } 02419 return side; 02420 } 02421 02422 static void gen_fields(GVJ_t * job, node_t * n, field_t * f) 02423 { 02424 int i; 02425 pointf AF[2], coord; 02426 02427 if (f->lp) { 02428 f->lp->pos = add_pointf(mid_pointf(f->b.LL, f->b.UR), ND_coord(n)); 02429 emit_label(job, EMIT_NLABEL, f->lp); 02430 pencolor(job, n); 02431 } 02432 02433 coord = ND_coord(n); 02434 for (i = 0; i < f->n_flds; i++) { 02435 if (i > 0) { 02436 if (f->LR) { 02437 AF[0] = f->fld[i]->b.LL; 02438 AF[1].x = AF[0].x; 02439 AF[1].y = f->fld[i]->b.UR.y; 02440 } else { 02441 AF[1] = f->fld[i]->b.UR; 02442 AF[0].x = f->fld[i]->b.LL.x; 02443 AF[0].y = AF[1].y; 02444 } 02445 AF[0] = add_pointf(AF[0], coord); 02446 AF[1] = add_pointf(AF[1], coord); 02447 gvrender_polyline(job, AF, 2); 02448 } 02449 gen_fields(job, n, f->fld[i]); 02450 } 02451 } 02452 02453 static void record_gencode(GVJ_t * job, node_t * n) 02454 { 02455 obj_state_t *obj = job->obj; 02456 boxf BF; 02457 pointf AF[4]; 02458 int style; 02459 field_t *f; 02460 int doMap = (obj->url || obj->explicit_tooltip); 02461 int filled; 02462 char* clrs[2]; 02463 02464 f = (field_t *) ND_shape_info(n); 02465 BF = f->b; 02466 BF.LL.x += ND_coord(n).x; 02467 BF.LL.y += ND_coord(n).y; 02468 BF.UR.x += ND_coord(n).x; 02469 BF.UR.y += ND_coord(n).y; 02470 02471 if (doMap && !(job->flags & EMIT_CLUSTERS_LAST)) 02472 gvrender_begin_anchor(job, 02473 obj->url, obj->tooltip, obj->target, 02474 obj->id); 02475 style = stylenode(job, n); 02476 pencolor(job, n); 02477 if (style & FILLED) { 02478 char* fillcolor = findFill (n); 02479 if (findStopColor (fillcolor, clrs)) { 02480 gvrender_set_fillcolor(job, clrs[0]); 02481 if (clrs[1]) 02482 gvrender_set_gradient_vals(job,clrs[1],late_int(n,N_gradientangle,0,0)); 02483 else 02484 gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(n,N_gradientangle,0,0)); 02485 if (style & RADIAL) 02486 filled = RGRADIENT; 02487 else 02488 filled = GRADIENT; 02489 } 02490 else { 02491 filled = FILL; 02492 gvrender_set_fillcolor(job, fillcolor); 02493 } 02494 } 02495 else filled = FALSE; 02496 02497 if (streq(ND_shape(n)->name, "Mrecord")) 02498 style |= ROUNDED; 02499 if (SPECIAL_CORNERS(style)) { 02500 AF[0] = BF.LL; 02501 AF[2] = BF.UR; 02502 AF[1].x = AF[2].x; 02503 AF[1].y = AF[0].y; 02504 AF[3].x = AF[0].x; 02505 AF[3].y = AF[2].y; 02506 round_corners(job, AF, 4, style, filled); 02507 } else { 02508 gvrender_box(job, BF, filled); 02509 } 02510 02511 gen_fields(job, n, f); 02512 02513 if (doMap) { 02514 if (job->flags & EMIT_CLUSTERS_LAST) 02515 gvrender_begin_anchor(job, 02516 obj->url, obj->tooltip, obj->target, 02517 obj->id); 02518 gvrender_end_anchor(job); 02519 } 02520 } 02521 02522 static shape_desc **UserShape; 02523 static int N_UserShape; 02524 02525 shape_desc *find_user_shape(const char *name) 02526 { 02527 int i; 02528 if (UserShape) { 02529 for (i = 0; i < N_UserShape; i++) { 02530 if (streq(UserShape[i]->name, name)) 02531 return UserShape[i]; 02532 } 02533 } 02534 return NULL; 02535 } 02536 02537 static shape_desc *user_shape(char *name) 02538 { 02539 int i; 02540 shape_desc *p; 02541 02542 if ((p = find_user_shape(name))) 02543 return p; 02544 i = N_UserShape++; 02545 UserShape = ALLOC(N_UserShape, UserShape, shape_desc *); 02546 p = UserShape[i] = NEW(shape_desc); 02547 *p = Shapes[0]; 02548 p->name = strdup(name); 02549 if (Lib == NULL && !streq(name, "custom")) { 02550 agerr(AGWARN, "using %s for unknown shape %s\n", Shapes[0].name, 02551 p->name); 02552 p->usershape = FALSE; 02553 } else { 02554 p->usershape = TRUE; 02555 } 02556 return p; 02557 } 02558 02559 shape_desc *bind_shape(char *name, node_t * np) 02560 { 02561 shape_desc *ptr, *rv = NULL; 02562 const char *str; 02563 02564 str = safefile(agget(np, "shapefile")); 02565 /* If shapefile is defined and not epsf, set shape = custom */ 02566 if (str && !streq(name, "epsf")) 02567 name = "custom"; 02568 if (!streq(name, "custom")) { 02569 for (ptr = Shapes; ptr->name; ptr++) { 02570 if (streq(ptr->name, name)) { 02571 rv = ptr; 02572 break; 02573 } 02574 } 02575 } 02576 if (rv == NULL) 02577 rv = user_shape(name); 02578 return rv; 02579 } 02580 02581 static boolean epsf_inside(inside_t * inside_context, pointf p) 02582 { 02583 pointf P; 02584 double x2; 02585 node_t *n = inside_context->s.n; 02586 02587 P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n))); 02588 x2 = ND_ht(n) / 2; 02589 return ((P.y >= -x2) && (P.y <= x2) && (P.x >= -ND_lw(n)) 02590 && (P.x <= ND_rw(n))); 02591 } 02592 02593 static void epsf_gencode(GVJ_t * job, node_t * n) 02594 { 02595 obj_state_t *obj = job->obj; 02596 epsf_t *desc; 02597 int doMap = (obj->url || obj->explicit_tooltip); 02598 02599 desc = (epsf_t *) (ND_shape_info(n)); 02600 if (!desc) 02601 return; 02602 02603 if (doMap && !(job->flags & EMIT_CLUSTERS_LAST)) 02604 gvrender_begin_anchor(job, 02605 obj->url, obj->tooltip, obj->target, 02606 obj->id); 02607 if (desc) 02608 fprintf(job->output_file, 02609 "%.5g %.5g translate newpath user_shape_%d\n", 02610 ND_coord(n).x + desc->offset.x, 02611 ND_coord(n).y + desc->offset.y, desc->macro_id); 02612 ND_label(n)->pos = ND_coord(n); 02613 02614 emit_label(job, EMIT_NLABEL, ND_label(n)); 02615 if (doMap) { 02616 if (job->flags & EMIT_CLUSTERS_LAST) 02617 gvrender_begin_anchor(job, 02618 obj->url, obj->tooltip, obj->target, 02619 obj->id); 02620 gvrender_end_anchor(job); 02621 } 02622 } 02623 02624 static char *side_port[] = { "s", "e", "n", "w" }; 02625 02626 static point cvtPt(pointf p, int rankdir) 02627 { 02628 pointf q = { 0, 0 }; 02629 point Q; 02630 02631 switch (rankdir) { 02632 case RANKDIR_TB: 02633 q = p; 02634 break; 02635 case RANKDIR_BT: 02636 q.x = p.x; 02637 q.y = -p.y; 02638 break; 02639 case RANKDIR_LR: 02640 q.y = p.x; 02641 q.x = -p.y; 02642 break; 02643 case RANKDIR_RL: 02644 q.y = p.x; 02645 q.x = p.y; 02646 break; 02647 } 02648 PF2P(q, Q); 02649 return Q; 02650 } 02651 02652 /* closestSide: 02653 * Resolve unspecified compass-point port to best available port. 02654 * At present, this finds the available side closest to the center 02655 * of the other port. 02656 * 02657 * This could be improved: 02658 * - if other is unspecified, do them together 02659 * - if dot, bias towards bottom of one to top of another, if possible 02660 * - if line segment from port centers uses available sides, use these 02661 * or center. (This latter may require spline routing to cooperate.) 02662 */ 02663 static char *closestSide(node_t * n, node_t * other, port * oldport) 02664 { 02665 boxf b; 02666 int rkd = GD_rankdir(agraphof(n)->root); 02667 point p = { 0, 0 }; 02668 point pt = cvtPt(ND_coord(n), rkd); 02669 point opt = cvtPt(ND_coord(other), rkd); 02670 int sides = oldport->side; 02671 char *rv = NULL; 02672 int i, d, mind = 0; 02673 02674 if ((sides == 0) || (sides == (TOP | BOTTOM | LEFT | RIGHT))) 02675 return rv; /* use center */ 02676 02677 if (oldport->bp) { 02678 b = *oldport->bp; 02679 } else { 02680 if (GD_flip(agraphof(n))) { 02681 b.UR.x = ND_ht(n) / 2; 02682 b.LL.x = -b.UR.x; 02683 b.UR.y = ND_lw(n); 02684 b.LL.y = -b.UR.y; 02685 } else { 02686 b.UR.y = ND_ht(n) / 2; 02687 b.LL.y = -b.UR.y; 02688 b.UR.x = ND_lw(n); 02689 b.LL.x = -b.UR.x; 02690 } 02691 } 02692 02693 for (i = 0; i < 4; i++) { 02694 if ((sides & (1 << i)) == 0) 02695 continue; 02696 switch (i) { 02697 case 0: 02698 p.y = b.LL.y; 02699 p.x = (b.LL.x + b.UR.x) / 2; 02700 break; 02701 case 1: 02702 p.x = b.UR.x; 02703 p.y = (b.LL.y + b.UR.y) / 2; 02704 break; 02705 case 2: 02706 p.y = b.UR.y; 02707 p.x = (b.LL.x + b.UR.x) / 2; 02708 break; 02709 case 3: 02710 p.x = b.LL.x; 02711 p.y = (b.LL.y + b.UR.y) / 2; 02712 break; 02713 } 02714 p.x += pt.x; 02715 p.y += pt.y; 02716 d = DIST2(p, opt); 02717 if (!rv || (d < mind)) { 02718 mind = d; 02719 rv = side_port[i]; 02720 } 02721 } 02722 return rv; 02723 } 02724 02725 port resolvePort(node_t * n, node_t * other, port * oldport) 02726 { 02727 port rv; 02728 char *compass = closestSide(n, other, oldport); 02729 02730 /* transfer name pointer; all other necessary fields will be regenerated */ 02731 rv.name = oldport->name; 02732 compassPort(n, oldport->bp, &rv, compass, oldport->side, NULL); 02733 02734 return rv; 02735 } 02736 02737 void resolvePorts(edge_t * e) 02738 { 02739 if (ED_tail_port(e).dyna) 02740 ED_tail_port(e) = 02741 resolvePort(agtail(e), aghead(e), &ED_tail_port(e)); 02742 if (ED_head_port(e).dyna) 02743 ED_head_port(e) = 02744 resolvePort(aghead(e), agtail(e), &ED_head_port(e)); 02745 }
1.7.5