Graphviz  2.29.20120524.0446
lib/common/shapes.c
Go to the documentation of this file.
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 }