/*
This software may only be used by you under license from AT&T Corp.
("AT&T"). A copy of AT&T's Source Code Agreement is available at
AT&T's Internet website having the URL:
<http://www.research.att.com/sw/tools/graphviz/license/source.html>
If you received this software without first entering into a license
with AT&T, you have an infringing copy of this software and cannot use
it without violating AT&T's intellectual property rights.
*/
#pragma prototyped
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "render.h"
#include "utils.h"
#define FILLED (1 << 0)
#define ROUNDED (1 << 1)
#define DIAGONALS (1 << 2)
#define AUXLABELS (1 << 3)
#define INVISIBLE (1 << 4)
#define RBCONST 12
#define RBCURVE .5
#ifndef HAVE_SINCOS
void sincos(x,s,c) double x,*s,*c; { *s = sin(x); *c = cos(x); }
#else
extern void sincos(double x, double *s, double *c);
#endif
static port_t Center = { {0,0}, -1, 0, 0, 0};
#define ATTR_SET(a,n) ((a) && (*(agxget(n,a->index)) != '\0'))
#define DEF_POINT 0.05
/* extra null character needed to avoid style emitter from thinking
* there are arguments.
*/
static char* point_style[3] = {"invis\0","filled\0",0};
static shape_desc* point_desc;
/* forward declarations of functions used in shapes tables */
/* static void poly_init(node_t *); */
/* static void point_init(node_t *); */
/* static void record_init(node_t *); */
static void poly_free(node_t *);
static port_t poly_port(node_t*, char *);
static int poly_inside(node_t*, pointf, edge_t *);
static void poly_gencode(node_t*);
#ifdef NOT_READY_YET
static void poly_shape_init(node_t *);
static void shape_free(node_t *);
static port_t shape_port(node_t*, char *);
static int shape_inside(node_t*, pointf, edge_t *);
static void shape_gencode(node_t*);
#endif
static void record_free(node_t *);
static port_t record_port(node_t*, char *);
static int record_inside(node_t*, pointf, edge_t *);
static int record_path(node_t* n, edge_t* e, int pt, box rv[], int* kptr);
static void record_gencode(node_t*);
#ifdef NOT_READY_YET
extern void tbl_init(node_t *);
extern void tbl_free(node_t *);
extern port_t tbl_port(node_t*, char *);
extern int tbl_inside(node_t*, pointf, edge_t *);
extern int tbl_path(node_t* n, edge_t* e, int pt, box rv[], int* kptr);
extern void tbl_gencode(node_t*);
#endif
static int epsf_inside(node_t*, pointf, edge_t *);
/* polygon descriptions. "polygon" with 0 sides takes all user control */
/* regul perip sides orien disto skew */
static polygon_t p_polygon = { FALSE, 1, 0, 0., 0., 0. };
/* builtin polygon descriptions */
static polygon_t p_ellipse = { FALSE, 1, 1, 0., 0., 0. };
static polygon_t p_circle = { TRUE, 1, 1, 0., 0., 0. };
static polygon_t p_egg = { FALSE, 1, 1, 0., -.3, 0. };
static polygon_t p_triangle = { FALSE, 1, 3, 0., 0., 0. };
static polygon_t p_box = { FALSE, 1, 4, 0., 0., 0. };
static polygon_t p_plaintext = { FALSE, 0, 4, 0., 0., 0. };
static polygon_t p_diamond = { FALSE, 1, 4, 45., 0., 0. };
static polygon_t p_trapezium = { FALSE, 1, 4, 0., -.4, 0. };
static polygon_t p_parallelogram = { FALSE, 1, 4, 0., 0., .6 };
static polygon_t p_house = { FALSE, 1, 5, 0., -.64, 0. };
static polygon_t p_pentagon = { FALSE, 1, 5, 0., 0., 0. };
static polygon_t p_hexagon = { FALSE, 1, 6, 0., 0., 0. };
static polygon_t p_septagon = { FALSE, 1, 7, 0., 0., 0. };
static polygon_t p_octagon = { FALSE, 1, 8, 0., 0., 0. };
/* redundant and undocumented builtin polygons */
static polygon_t p_doublecircle = { TRUE, 2, 1, 0., 0., 0. };
static polygon_t p_invtriangle = { FALSE, 1, 3, 180., 0., 0. };
static polygon_t p_invtrapezium = { FALSE, 1, 4, 180., -.4, 0. };
static polygon_t p_invhouse = { FALSE, 1, 5, 180., -.64, 0. };
static polygon_t p_doubleoctagon = { FALSE, 2, 8, 0., 0., 0. };
static polygon_t p_tripleoctagon = { FALSE, 3, 8, 0., 0., 0. };
static polygon_t p_Mdiamond = { FALSE, 1, 4, 45., 0., 0. ,DIAGONALS|AUXLABELS};
static polygon_t p_Msquare = { TRUE, 1, 4, 0., 0., 0. ,DIAGONALS};
static polygon_t p_Mcircle = { TRUE, 1, 1, 0., 0., 0.,DIAGONALS|AUXLABELS};
/*
* every shape has these functions:
*
* void SHAPE_init(node_t *n)
* initialize the shape (usually at least its size).
* port_t SHAPE_port(node_t *n, char *portname)
* return the aiming point and slope (if constrained)
* of a port.
* int SHAPE_inside(node_t *n, pointf p, edge_t *e);
* test if point is inside the node shape which is
* assumed convex.
* the point is relative to the node center. the edge
* is passed in case the port affects spline clipping.
* void SHAPE_code(node_t *n)
* generate graphics code for a node.
* int SHAPE_path(node_t *n, edge_t *e, int pt, box path[], int *nbox)
* create a path for the port of e that touches n,
* return side
*
* some shapes, polygons in particular, use additional shape control data *
*
*/
/* this needs an overhaul */
static shape_desc Shapes[] = { /* first entry is default for no such shape */
{"box" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_box },
{"polygon" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_polygon },
{"ellipse" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_ellipse },
{"circle" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_circle },
{"point" ,point_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_circle },
{"egg" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_egg },
{"triangle" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_triangle },
{"plaintext" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_plaintext },
{"diamond" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_diamond },
{"trapezium" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_trapezium },
{"parallelogram",poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_parallelogram },
{"house" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_house },
{"pentagon" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_pentagon },
{"hexagon" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_hexagon },
{"septagon" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_septagon },
{"octagon" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_octagon },
/* redundant and undocumented builtin polygons */
{"rect" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_box },
{"rectangle" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_box },
{"doublecircle" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_doublecircle },
{"doubleoctagon",poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_doubleoctagon },
{"tripleoctagon",poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_tripleoctagon },
{"invtriangle" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_invtriangle },
{"invtrapezium" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_invtrapezium },
{"invhouse" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_invhouse },
{"Mdiamond" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_Mdiamond },
{"Msquare" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_Msquare },
{"Mcircle" ,poly_init,poly_free,poly_port,poly_inside,NULL,poly_gencode,&p_Mcircle },
/* *** shapes other than polygons *** */
{"record" ,record_init,record_free,record_port,record_inside,record_path,record_gencode,NULL},
{"Mrecord" ,record_init,record_free,record_port,record_inside,record_path,record_gencode,NULL},
{"epsf" ,epsf_init,epsf_free,poly_port,epsf_inside,NULL,epsf_gencode,NULL},
#ifdef NOT_READY_YET
{"table" ,tbl_init,tbl_free,tbl_port,tbl_inside,tbl_path,tbl_gencode,NULL},
#endif
{NULL ,NULL,NULL,NULL,NULL,NULL,NULL,NULL}
};
#if 0 /* not used */
static point
flip_pt(point p)
{ int t = p.x; p.x = -p.y; p.y = t; return p; }
#endif
static point
invflip_pt(point p)
{ int t = p.x; p.x = p.y; p.y = -t; return p; }
static pointf
flip_ptf(pointf p)
{ double t = p.x; p.x = -p.y; p.y = t; return p; }
static void
storeline(textlabel_t *lp, char *line, char terminator,graph_t *g)
{
double width = 0.0;
int iwidth = 0;
lp->line = ALLOC(lp->nlines+2,lp->line,textline_t);
lp->line[lp->nlines].str = line;
if (line) {
if (CodeGen && CodeGen->textsize)
iwidth = (CodeGen->textsize(line, lp->fontname,lp->fontsize).x);
if (!iwidth)
iwidth = estimate_textsize(line, lp->fontname,lp->fontsize).x;
width = (double)iwidth;
}
lp->line[lp->nlines].width = width;
lp->line[lp->nlines].just = terminator;
lp->nlines++;
if (width > 0) {
width += lp->fontsize; /* margins */
width = PS2INCH(width);
if (lp->dimen.x < width) lp->dimen.x = width;
}
/* recalculate total height, including a small extra margin
* for top and bottom */
lp->dimen.y = PS2INCH(lp->nlines*(int)(lp->fontsize*LINESPACING) + 4);
}
/* compiles <str> into a label <lp> and returns its bounding box size. */
pointf label_size(char *str, textlabel_t *lp, graph_t *g)
{
char c,*p,*line,*lineptr;
unsigned char byte = 0x00;
if (*str == '\0') return lp->dimen;
line = lineptr = NULL;
p = str;
line = lineptr = N_GNEW(strlen(p) + 1,char);
*line = 0;
while ((c = *p++)) {
byte = (unsigned int )c;
if (c & ~0x7f) GD_has_Latin1char(g) = TRUE;
//Fix some Double Character error for Big5 (Start)
if (0xA1 <= byte && byte <= 0xFE) {
*lineptr++ = c;
c = *p++;
*lineptr++ = c;
//Fix some Double Character error for Big5 (End)
}else{
if (c == '\\') {
switch (*p) {
case 'n': case 'l': case 'r':
*lineptr++ = '\0';
storeline(lp,line,*p,g);
line = lineptr;
break;
default:
*lineptr++ = *p;
}
if (*p) p++;
/* tcldot can enter real linend characters */
} else if (c == '\n') {
*lineptr++ = '\0';
storeline(lp,line,'n',g);
line = lineptr;
}else{
*lineptr++ = c;
}
}
}
if (line != lineptr) {
*lineptr++ = '\0';
storeline(lp,line,'n',g);
}
return lp->dimen;
}
static void
unrecognized(node_t* n, char* p)
{
agerr(AGWARN, "node %s, port %s unrecognized\n",n->name,p);
}
#define GAP (PS2INCH(4.))
static double quant(double val, double q)
{
int i;
i = val / q;
if (i * q + .00001 < val) i++;
return i * q;
}
static int same_side(pointf p0, pointf p1, pointf L0, pointf L1)
{
int s0,s1;
double a,b,c;
/* a x + b y = c */
a = -(L1.y - L0.y);
b = (L1.x - L0.x);
c = a * L0.x + b * L0.y;
s0 = (a*p0.x + b*p0.y - c >= 0);
s1 = (a*p1.x + b*p1.y - c >= 0);
return (s0 == s1);
}
void pencolor(node_t *n)
{
char *color;
color = late_nnstring(n,N_color,"");
if (color[0])
CodeGen->set_pencolor(color);
}
static
char* findFill(node_t *n)
{
char *color;
color = late_nnstring(n,N_fillcolor,"");
if (! color[0]) {
/* for backward compatibilty, default fill is same as pen */
color = late_nnstring(n,N_color,"");
if (! color[0]) {
if (ND_shape(n) == point_desc) {
color = "black";
}
else {
color = (Output_lang == MIF ? "black" : DEFAULT_FILL);
}
}
}
return color;
}
void fillcolor(node_t *n)
{
CodeGen->set_fillcolor(findFill(n));
}
static char**
checkStyle (node_t* n, int* flagp)
{
char* style;
char** pstyle = 0;
int istyle = 0;
polygon_t* poly;
style = late_nnstring(n,N_style,"");
if (style[0]) {
int i;
pstyle = parse_style(style);
for (i = 0; pstyle[i]; i++) {
if (strcmp(pstyle[i],"filled") == 0) {istyle |= FILLED;}
else if (strcmp(pstyle[i],"rounded") == 0) {istyle |= ROUNDED;}
else if (strcmp(pstyle[i],"diagonals") == 0) {istyle |= DIAGONALS;}
else if (strcmp(pstyle[i],"invis") == 0) {istyle |= INVISIBLE;}
}
}
if ((poly = ND_shape(n)->polygon)) istyle |= poly->option;
*flagp = istyle;
return pstyle;
}
int stylenode(node_t* n)
{
char** pstyle;
int istyle;
if ((pstyle = checkStyle (n, &istyle)))
CodeGen->set_style(pstyle);
return istyle;
}
static void hack1(node_t* n, char* str, int k)
{
point p;
double fontsize;
textline_t fake;
fontsize = ND_label(n)->fontsize*.8; /* magic? */
p.x = ND_coord_i(n).x - (strlen(str) * ND_label(n)->fontsize) / 2;
p.y = ND_coord_i(n).y + (k * (ND_ht_i(n) - ND_label(n)->fontsize - 2)) / 2;
CodeGen->begin_context();
CodeGen->set_font(ND_label(n)->fontname,fontsize);
fake.str = str;
fake.width = strlen(str) * fontsize; /* ugh */
fake.just = 0; /* does this field do anything? SCN */
CodeGen->textline(p,&fake);
CodeGen->end_context();
}
static void Mlabel_hack(node_t* n)
{
char *str;
if ((str = agget(n,"toplabel"))) hack1(n,str,1);
if ((str = agget(n,"bottomlabel"))) hack1(n,str,-1);
}
static void Mcircle_hack(node_t* n)
{
double x,y;
point A[2],p;
y = .7500;
x = .6614; /* x^2 + y^2 = 1.0 */
p.y = y * ND_ht_i(n) / 2.0;
p.x = ND_rw_i(n) * x; /* assume node is symmetric */
A[0] = add_points(p,ND_coord_i(n));
A[1].y = A[0].y; A[1].x = A[0].x - 2*p.x;
CodeGen->polyline(A,2);
A[0].y -= 2*p.y; A[1].y = A[0].y;
CodeGen->polyline(A,2);
}
static point interpolate(double t, point p0, point p1)
{
point rv;
rv.x = p0.x + t * (p1.x - p0.x);
rv.y = p0.y + t * (p1.y - p0.y);
return rv;
}
static void round_corners(node_t *nn, point *A, int n, int style)
{
point *B,C[2],p0,p1;
double d,dx,dy,t;
int i,seg,mode;
if (style & DIAGONALS) mode = DIAGONALS;
else mode = ROUNDED;
B = N_NEW(4*n+4,point);
i = 0;
for (seg = 0; seg < n; seg++) {
p0 = A[seg];
if (seg < n - 1) p1 = A[seg+1];
else p1 = A[0];
dx = p1.x - p0.x;
dy = p1.y - p0.y;
d = sqrt(dx*dx + dy*dy);
/*t = ((mode == ROUNDED)? RBCONST / d : .5);*/
t = RBCONST / d;
if (mode != ROUNDED) B[i++] = p0;
if (mode == ROUNDED) B[i++] = interpolate(RBCURVE*t,p0,p1);
B[i++] = interpolate(t,p0,p1);
B[i++] = interpolate(1.0 - t,p0,p1);
if (mode == ROUNDED) B[i++] = interpolate(1.0 - RBCURVE*t,p0,p1);
}
B[i++] = B[0];
B[i++] = B[1];
B[i++] = B[2];
if (mode == ROUNDED) {
for (seg = 0; seg < n; seg++) {
CodeGen->polyline(B + 4*seg+1,2);
CodeGen->beziercurve(B + 4*seg+2,4,FALSE,FALSE);
}
}
else { /* diagonals are weird. rewrite someday. */
pencolor(nn);
if (style & FILLED) fillcolor(nn); /* emit fill color */
CodeGen->polygon(A,n,style&FILLED);
for (seg = 0; seg < n; seg++) {
#ifdef NOTDEF
C[0] = B[3 * seg]; C[1] = B[3 * seg + 3];
CodeGen->polyline(C,2);
#endif
C[0] = B[3 * seg + 2]; C[1] = B[3 * seg + 4];
CodeGen->polyline(C,2);
}
}
free(B);
}
/*=============================poly start=========================*/
void poly_init(node_t* n)
{
pointf dimen;
pointf P,Q,R;
pointf *vertices;
double temp,alpha,beta,gamma,delta,xb,yb;
double orientation,distortion,skew;
double sectorangle, sidelength, skewdist, gdistortion, gskew;
double angle, sinx, cosx, xmax, ymax, scalex, scaley;
int regular,peripheries,sides;
int i,j,outp;
polygon_t *poly=NEW(polygon_t);
regular = ND_shape(n)->polygon->regular;
peripheries = ND_shape(n)->polygon->peripheries;
sides = ND_shape(n)->polygon->sides;
orientation = ND_shape(n)->polygon->orientation;
skew = ND_shape(n)->polygon->skew;
distortion = ND_shape(n)->polygon->distortion;
regular |= mapbool(agget(n,"regular"));
peripheries = late_int(n,N_peripheries,peripheries,0);
orientation += late_double(n,N_orientation,0.0,-360.0);
if (sides==0) { /* not for builtins */
skew = late_double(n,N_skew,0.0,-100.0);
sides = late_int(n,N_sides,4,0);
distortion = late_double(n,N_distortion,0.0,-100.0);
}
/* get label dimensions */
dimen = ND_label(n)->dimen;
if (mapbool(late_string(n,N_fixed,"false"))) {
if ((ND_width(n) < dimen.x) || (ND_height(n) < dimen.y))
agerr(AGWARN, "node '%s' size too small for label\n",
n->name);
dimen.x = dimen.y = 0;
}
else {
if (ND_shape(n)->usershape && CodeGen && CodeGen->usershapesize) {
point imagesize;
char* sfile = agget(n,"shapefile");
imagesize = CodeGen->usershapesize(n,sfile);
if ((imagesize.x == 0) && (imagesize.y == 0)) {
agerr(AGERR, "No or improper shapefile=\"%s\" for user-defined shape=\"%s\" on node \"%s\"\n",
(sfile ? sfile : "<nil>"), agget(n,"shape"), n->name);
}
dimen.x = MAX(dimen.x,PS2INCH(imagesize.x));
dimen.y = MAX(dimen.y,PS2INCH(imagesize.y));
}
}
/* quantization */
if ((temp = GD_drawing(n->graph)->quantum) > 0.0) {
dimen.x = quant(dimen.x,temp);
dimen.y = quant(dimen.y,temp);
}
/* make square if necessary */
if (regular) {
/* make x and y dimensions equal */
ND_width(n) = ND_height(n) = MIN(ND_width(n),ND_height(n));
xb = yb = MAX(dimen.x,dimen.y);
} else {
xb = dimen.x; yb = dimen.y;
}
/* I don't know how to distort or skew ellipses in postscript */
/* Convert request to a polygon with a large number of sides */
if ((sides<=2) && ((distortion!=0.) || (skew!=0.))) {
sides = 120;
}
/* adjust bounding box so that label fits in inner ellipse */
/* this will change the symmetry of the bounding box */
/* adjust for inner to outer diameter of polygon */
if (sides>2) { /* except ellipses */
temp = cos(PI/sides);
xb /= temp; yb /= temp;
}
if ( (sides!=4)
|| ((ROUND(orientation)%90)!=0)
|| (distortion!=0.)
|| (skew!=0.) ) {
if (yb>xb) temp = xb * (sqrt(2.) - 1.);
else temp = yb * (sqrt(2.) - 1.);
xb += temp; yb += temp;
}
xb=MAX(ND_width(n),xb); yb=MAX(ND_height(n),yb);
outp=peripheries;
if (peripheries<1) outp=1;
if (sides<3) { /* ellipses */
sides=1;
vertices=N_NEW(outp,pointf);
P.x=xb/2.; P.y=yb/2.;
vertices[0] = P;
if (peripheries>1) {
for (j=1; j<peripheries; j++) {
P.x += GAP; P.y += GAP;
vertices[j] = P;
}
xb=2.*P.x; yb=2.*P.y;
}
} else {
vertices=N_NEW(outp*sides,pointf);
sectorangle = 2.*PI/sides;
sidelength = sin(sectorangle/2.);
skewdist = hypot(fabs(distortion)+fabs(skew),1.);
gdistortion = distortion*sqrt(2.)/cos(sectorangle/2.);
gskew = skew/2.;
angle = (sectorangle-PI)/2.;
sincos(angle,&sinx,&cosx);
R.x = .5*cosx; R.y = .5*sinx;
xmax=ymax=0.;
angle += (PI-sectorangle)/2.;
for (i=0; i<sides; i++) {
/*next regular vertex*/
angle += sectorangle;
sincos(angle,&sinx,&cosx);
R.x += sidelength*cosx; R.y += sidelength*sinx;
/*distort and skew*/
P.x = R.x*(skewdist+R.y*gdistortion)+R.y*gskew;
P.y = R.y;
/*orient P.x,P.y*/
alpha = RADIANS(orientation)+atan2(P.y,P.x);
sincos(alpha,&sinx,&cosx);
P.x = P.y = hypot(P.x,P.y);
P.x *= cosx; P.y *= sinx;
/*scale for label*/
P.x *= xb; P.y *= yb;
/*find max for bounding box*/
xmax = MAX(fabs(P.x),xmax); ymax = MAX(fabs(P.y),ymax);
/* store result in array of points */
vertices[i] = P;
}
/* apply minimum dimensions */
xmax *=2.; ymax *=2.;
xb=MAX(ND_width(n),xmax); yb=MAX(ND_height(n),ymax);
scalex=xb/xmax; scaley=yb/ymax;
for (i=0; i<sides; i++) {
P = vertices[i];
P.x *= scalex; P.y *= scaley;
vertices[i] = P;
}
if (peripheries>1) {
Q = vertices[(sides-1)];
R = vertices[0];
beta = atan2(R.y-Q.y,R.x-Q.x);
for (i=0; i<sides; i++) {
/*for each vertex find the bisector*/
P = Q; Q = R; R = vertices[(i+1)%sides];
alpha = beta; beta = atan2(R.y-Q.y,R.x-Q.x);
gamma = (alpha+PI-beta)/2.;
/*find distance along bisector to*/
/*intersection of next periphery*/
temp = GAP/sin(gamma);
/*convert this distance to x and y*/
delta = alpha-gamma;
sincos(delta,&sinx,&cosx);
sinx *= temp; cosx *= temp;
/*save the vertices of all the*/
/*peripheries at this base vertex*/
for (j=1; j<peripheries; j++) {
Q.x += cosx; Q.y += sinx;
vertices[i+j*sides] = Q;
}
}
for (i=0; i<sides; i++) {
P = vertices[i+(peripheries-1)*sides];
xb = MAX(2.*fabs(P.x),xb);
yb = MAX(2.*fabs(P.y),yb);
}
}
}
poly->regular = regular;
poly->peripheries = peripheries;
poly->sides = sides;
poly->orientation = orientation;
poly->skew = skew;
poly->distortion = distortion;
poly->vertices = vertices;
ND_width(n) = xb;
ND_height(n) = yb;
ND_shape_info(n) = (void*) poly;
}
static void poly_free(node_t *n)
{
polygon_t* p = ND_shape_info(n);
if (p) {
free(p->vertices);
free(p);
}
}
static int poly_inside(node_t* n, pointf p, edge_t* e)
{
static polygon_t *poly;
static int last,outp,sides;
static node_t *lastn;
static pointf O;
static pointf *vertex;
static double xsize,ysize,scalex,scaley,box_URx,box_URy;
int i,i1,j,s;
pointf P,Q,R;
e = e;
P = (GD_left_to_right(n->graph)? flip_ptf(p) : p);
if (n != lastn) {
poly = (polygon_t*) ND_shape_info(n);
vertex = poly->vertices;
sides = poly->sides;
lastn = n;
/* get point and node size adjusted for rankdir=LR */
if (GD_left_to_right(n->graph)) {
ysize = ND_lw_i(n) + ND_rw_i(n); xsize = ND_ht_i(n);
}
else {
xsize = ND_lw_i(n) + ND_rw_i(n); ysize = ND_ht_i(n);
}
/* scale */
if (xsize == 0.0) xsize = 1.0;
if (ysize == 0.0) ysize = 1.0;
scalex = ND_width(n)/xsize; scaley = ND_height(n)/ysize;
box_URx = ND_width(n)/2.0; box_URy = ND_height(n)/2.0;
/* index to outer-periphery */
outp=(poly->peripheries-1)*sides;
if (outp<0) outp=0;
}
/* scale */
P.x *= scalex; P.y *= scaley;
/* inside bounding box? */
if ((fabs(P.x)>box_URx) || (fabs(P.y)>box_URy)) return FALSE;
/* ellipses */
if (sides<=2) return (hypot(P.x/box_URx,P.y/box_URy)<1.);
/* use fast test in case we are converging on a segment */
i = last % sides; /*in case last left over from larger polygon*/
i1 = (i + 1) % sides;
Q = vertex[i+outp]; R = vertex[i1+outp];
if ( !(same_side(P,O,Q,R))) return FALSE;
if ( (s=same_side(P,Q,R,O)) && (same_side(P,R,O,Q))) return TRUE;
for (j = 1; j < sides; j++) {
if (s) {
i = i1; i1 = (i + 1) % sides;
} else {
i1 = i; i = (i + sides - 1) % sides;
}
if ( !(same_side(P,O,vertex[i+outp],vertex[i1+outp]))) {
last = i;
return FALSE;
}
}
last = i; /* in case next edge is to same side */
return TRUE;
}
static port_t poly_port(node_t* n, char* pname)
{
static char *points_of_compass[] =
{"n","ne","e","se","s","sw","w","nw",NULL};
static struct {signed char x,y;} a[] = {{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}};
int i,ht,wd;
port_t rv;
char *p;
if (*pname) pname++; /* skip over delim */
for (i = 0; (p = points_of_compass[i]); i++)
if (streq(p,pname)) break;
if (p == NULL) {
if (pname[0]) unrecognized(n,pname);
rv = Center;
}
else {
ht = ND_ht_i(n) / 2;
wd = ND_lw_i(n);
rv.p.x = a[i].x * wd;
rv.p.y = a[i].y * ht;
rv.order = (MC_SCALE * (ND_lw_i(n) + rv.p.x)) / (ND_lw_i(n) + ND_rw_i(n));
rv.constrained = FALSE;
rv.defined = TRUE;
}
return rv;
}
/* generic polygon gencode routine */
static void poly_gencode(node_t* n)
{
polygon_t *poly;
double xsize, ysize;
int i,j,peripheries,sides,style;
pointf P,*vertices;
static point *A;
static int A_size;
int filled;
poly = (polygon_t*) ND_shape_info(n);
vertices = poly->vertices;
sides = poly->sides;
peripheries = poly->peripheries;
if (A_size < sides) {A_size = sides + 5; A = ALLOC(A_size,A,point);}
CodeGen->begin_node(n);
CodeGen->begin_context();
/* prescale by 16.0 to help rounding trick below */
xsize = ((ND_lw_i(n) + ND_rw_i(n)) / ND_width(n)) * 16.0;
ysize = ((ND_ht_i(n)) / ND_height(n)) * 16.0;
/* this is bad, but it's because of how the VRML driver works */
#ifdef HAVE_LIBPNG
if ((CodeGen == &VRML_CodeGen) && (peripheries == 0)) {
peripheries = 1;
}
#endif
if (ND_shape(n) == point_desc) {
checkStyle (n, &style);
if (style & INVISIBLE) CodeGen->set_style(point_style);
else CodeGen->set_style(&point_style[1]);
style = FILLED;
}
else {
style = stylenode(n);
}
if (style & FILLED) {
fillcolor(n); /* emit fill color */
filled = 1;
}
else {
filled = 0;
}
/* if no boundary but filled, set boundary color to fill color */
if ((peripheries == 0) && filled) {
char* color;
peripheries = 1;
color = findFill (n);
if (color[0])
CodeGen->set_pencolor(color);
}
else pencolor(n); /* emit pen color */
if (ND_shape(n)->usershape && CodeGen->user_shape) {
for (i = 0; i < sides; i++) {
P = vertices[i];
/* simple rounding produces random results around .5
* this trick should clip off the random part.
* (note xsize/ysize prescaled by 16.0 above) */
A[i].x = ROUND(P.x * xsize) / 16;
A[i].y = ROUND(P.y * ysize) / 16;
if (sides > 2) {
A[i].x += ND_coord_i(n).x;
A[i].y += ND_coord_i(n).y;
}
}
CodeGen->user_shape(ND_shape(n)->name,A,sides,filled);
filled = 0;
}
for (j = 0; j < peripheries; j++) {
for (i = 0; i < sides; i++) {
P = vertices[i+j*sides];
/* simple rounding produces random results around .5
* this trick should clip off the random part.
* (note xsize/ysize prescaled by 16.0 above) */
A[i].x = ROUND(P.x * xsize) / 16;
A[i].y = ROUND(P.y * ysize) / 16;
if (sides > 2) {
A[i].x += ND_coord_i(n).x;
A[i].y += ND_coord_i(n).y;
}
}
if (sides <= 2) {
CodeGen->ellipse(ND_coord_i(n),A[0].x,A[0].y,filled);
if (style & DIAGONALS) {
Mcircle_hack(n);
}
}
else if (style & (ROUNDED | DIAGONALS)) {
round_corners(n,A,sides,style);
}
else {
CodeGen->polygon(A,sides,filled);
}
/* fill innermost periphery only */
filled = 0;
}
if (style & AUXLABELS) Mlabel_hack(n);
ND_label(n)->p = ND_coord_i(n);
emit_label(ND_label(n),n->graph);
CodeGen->end_context();
CodeGen->end_node();
}
/*=======================end poly======================================*/
/*=======================start shape======================================*/
#ifdef NOT_READY_YET
static void poly_shape_init(node_t* n)
{
pointf dimen;
pointf P,Q,R;
pointf *vertices;
double temp,alpha,beta,gamma,delta,xb,yb;
double orientation,distortion,skew;
double sectorangle, sidelength, skewdist, gdistortion, gskew;
double angle, sinx, cosx, xmax, ymax, scalex, scaley;
int regular,peripheries,sides;
int i,j,outp;
polygon_t *poly=NEW(polygon_t);
regular = ND_shape(n)->polygon->regular;
peripheries = ND_shape(n)->polygon->peripheries;
sides = ND_shape(n)->polygon->sides;
orientation = ND_shape(n)->polygon->orientation;
skew = ND_shape(n)->polygon->skew;
distortion = ND_shape(n)->polygon->distortion;
regular |= mapbool(agget(n,"regular"));
peripheries = late_int(n,N_peripheries,peripheries,0);
orientation += late_double(n,N_orientation,0.0,-360.0);
if (sides==0) { /* not for builtins */
skew = late_double(n,N_skew,0.0,-100.0);
sides = late_int(n,N_sides,4,0);
distortion = late_double(n,N_distortion,0.0,-100.0);
}
/* get label dimensions */
dimen = ND_label(n)->dimen;
if (mapbool(late_string(n,N_fixed,"false"))) {
if ((ND_width(n) < dimen.x) || (ND_height(n) < dimen.y))
agerr(AGWARN, "node '%s' size too small for label\n",
n->name);
dimen.x = dimen.y = 0;
}
else {
if (ND_shape(n)->usershape && CodeGen && CodeGen->usershapesize) {
point imagesize;
char* sfile = agget(n,"shapefile");
imagesize = CodeGen->usershapesize(n,sfile);
if ((imagesize.x == 0) && (imagesize.y == 0)) {
agerr(AGERR, "No or improper shapefile=\"%s\" for user-defined shape=\"%s\" on node \"%s\"\n",
(sfile ? sfile : "<nil>"), agget(n,"shape"), n->name);
}
dimen.x = MAX(dimen.x,PS2INCH(imagesize.x));
dimen.y = MAX(dimen.y,PS2INCH(imagesize.y));
}
}
/* quantization */
if ((temp = GD_drawing(n->graph)->quantum) > 0.0) {
dimen.x = quant(dimen.x,temp);
dimen.y = quant(dimen.y,temp);
}
/* make square if necessary */
if (regular) {
/* make x and y dimensions equal */
ND_width(n) = ND_height(n) = MIN(ND_width(n),ND_height(n));
xb = yb = MAX(dimen.x,dimen.y);
} else {
xb = dimen.x; yb = dimen.y;
}
/* I don't know how to distort or skew ellipses in postscript */
/* Convert request to a polygon with a large number of sides */
if ((sides<=2) && ((distortion!=0.) || (skew!=0.))) {
sides = 120;
}
/* adjust bounding box so that label fits in inner ellipse */
/* this will change the symmetry of the bounding box */
/* adjust for inner to outer diameter of polygon */
if (sides>2) { /* except ellipses */
temp = cos(PI/sides);
xb /= temp; yb /= temp;
}
if ( (sides!=4)
|| ((ROUND(orientation)%90)!=0)
|| (distortion!=0.)
|| (skew!=0.) ) {
if (yb>xb) temp = xb * (sqrt(2.) - 1.);
else temp = yb * (sqrt(2.) - 1.);
xb += temp; yb += temp;
}
xb=MAX(ND_width(n),xb); yb=MAX(ND_height(n),yb);
outp=peripheries;
if (peripheries<1) outp=1;
if (sides<3) { /* ellipses */
sides=1;
vertices=N_NEW(outp,pointf);
P.x=xb/2.; P.y=yb/2.;
vertices[0] = P;
if (peripheries>1) {
for (j=1; j<peripheries; j++) {
P.x += GAP; P.y += GAP;
vertices[j] = P;
}
xb=2.*P.x; yb=2.*P.y;
}
} else {
vertices=N_NEW(outp*sides,pointf);
sectorangle = 2.*PI/sides;
sidelength = sin(sectorangle/2.);
skewdist = hypot(fabs(distortion)+fabs(skew),1.);
gdistortion = distortion*sqrt(2.)/cos(sectorangle/2.);
gskew = skew/2.;
angle = (sectorangle-PI)/2.;
sincos(angle,&sinx,&cosx);
R.x = .5*cosx; R.y = .5*sinx;
xmax=ymax=0.;
angle += (PI-sectorangle)/2.;
for (i=0; i<sides; i++) {
/*next regular vertex*/
angle += sectorangle;
sincos(angle,&sinx,&cosx);
R.x += sidelength*cosx; R.y += sidelength*sinx;
/*distort and skew*/
P.x = R.x*(skewdist+R.y*gdistortion)+R.y*gskew;
P.y = R.y;
/*orient P.x,P.y*/
alpha = RADIANS(orientation)+atan2(P.y,P.x);
sincos(alpha,&sinx,&cosx);
P.x = P.y = hypot(P.x,P.y);
P.x *= cosx; P.y *= sinx;
/*scale for label*/
P.x *= xb; P.y *= yb;
/*find max for bounding box*/
xmax = MAX(fabs(P.x),xmax); ymax = MAX(fabs(P.y),ymax);
/* store result in array of points */
vertices[i] = P;
}
/* apply minimum dimensions */
xmax *=2.; ymax *=2.;
xb=MAX(ND_width(n),xmax); yb=MAX(ND_height(n),ymax);
scalex=xb/xmax; scaley=yb/ymax;
for (i=0; i<sides; i++) {
P = vertices[i];
P.x *= scalex; P.y *= scaley;
vertices[i] = P;
}
if (peripheries>1) {
Q = vertices[(sides-1)];
R = vertices[0];
beta = atan2(R.y-Q.y,R.x-Q.x);
for (i=0; i<sides; i++) {
/*for each vertex find the bisector*/
P = Q; Q = R; R = vertices[(i+1)%sides];
alpha = beta; beta = atan2(R.y-Q.y,R.x-Q.x);
gamma = (alpha+PI-beta)/2.;
/*find distance along bisector to*/
/*intersection of next periphery*/
temp = GAP/sin(gamma);
/*convert this distance to x and y*/
delta = alpha-gamma;
sincos(delta,&sinx,&cosx);
sinx *= temp; cosx *= temp;
/*save the vertices of all the*/
/*peripheries at this base vertex*/
for (j=1; j<peripheries; j++) {
Q.x += cosx; Q.y += sinx;
vertices[i+j*sides] = Q;
}
}
for (i=0; i<sides; i++) {
P = vertices[i+(peripheries-1)*sides];
xb = MAX(2.*fabs(P.x),xb);
yb = MAX(2.*fabs(P.y),yb);
}
}
}
poly->regular = regular;
poly->peripheries = peripheries;
poly->sides = sides;
poly->orientation = orientation;
poly->skew = skew;
poly->distortion = distortion;
poly->vertices = vertices;
ND_width(n) = xb;
ND_height(n) = yb;
ND_shape_info(n) = (void*) poly;
}
static void shape_free(node_t *n)
{
polygon_t* p = ND_shape_info(n);
if (p) {
free(p->vertices);
free(p);
}
}
static int shape_inside(node_t* n, pointf p, edge_t* e)
{
static polygon_t *poly;
static int last,outp,sides;
static node_t *lastn;
static pointf O;
static pointf *vertex;
static double xsize,ysize,scalex,scaley,box_URx,box_URy;
int i,i1,j,s;
pointf P,Q,R;
e = e;
P = (GD_left_to_right(n->graph)? flip_ptf(p) : p);
if (n != lastn) {
poly = (polygon_t*) ND_shape_info(n);
vertex = poly->vertices;
sides = poly->sides;
lastn = n;
/* get point and node size adjusted for rankdir=LR */
if (GD_left_to_right(n->graph)) {
ysize = ND_lw_i(n) + ND_rw_i(n); xsize = ND_ht_i(n);
}
else {
xsize = ND_lw_i(n) + ND_rw_i(n); ysize = ND_ht_i(n);
}
/* scale */
if (xsize == 0.0) xsize = 1.0;
if (ysize == 0.0) ysize = 1.0;
scalex = ND_width(n)/xsize; scaley = ND_height(n)/ysize;
box_URx = ND_width(n)/2.0; box_URy = ND_height(n)/2.0;
/* index to outer-periphery */
outp=(poly->peripheries-1)*sides;
if (outp<0) outp=0;
}
/* scale */
P.x *= scalex; P.y *= scaley;
/* inside bounding box? */
if ((fabs(P.x)>box_URx) || (fabs(P.y)>box_URy)) return FALSE;
/* ellipses */
if (sides<=2) return (hypot(P.x/box_URx,P.y/box_URy)<1.);
/* use fast test in case we are converging on a segment */
i = last % sides; /*in case last left over from larger polygon*/
i1 = (i + 1) % sides;
Q = vertex[i+outp]; R = vertex[i1+outp];
if ( !(same_side(P,O,Q,R))) return FALSE;
if ( (s=same_side(P,Q,R,O)) && (same_side(P,R,O,Q))) return TRUE;
for (j = 1; j < sides; j++) {
if (s) {
i = i1; i1 = (i + 1) % sides;
} else {
i1 = i; i = (i + sides - 1) % sides;
}
if ( !(same_side(P,O,vertex[i+outp],vertex[i1+outp]))) {
last = i;
return FALSE;
}
}
last = i; /* in case next edge is to same side */
return TRUE;
}
static port_t shape_port(node_t* n, char* pname)
{
static char *points_of_compass[] =
{"n","ne","e","se","s","sw","w","nw",NULL};
static struct {signed char x,y;} a[] = {{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}};
int i,ht,wd;
port_t rv;
char *p;
if (*pname) pname++; /* skip over delim */
for (i = 0; (p = points_of_compass[i]); i++)
if (streq(p,pname)) break;
if (p == NULL) {
if (pname[0]) unrecognized(n,pname);
rv = Center;
}
else {
ht = ND_ht_i(n) / 2;
wd = ND_lw_i(n);
rv.p.x = a[i].x * wd;
rv.p.y = a[i].y * ht;
rv.order = (MC_SCALE * (ND_lw_i(n) + rv.p.x)) / (ND_lw_i(n) + ND_rw_i(n));
rv.constrained = FALSE;
rv.defined = TRUE;
}
return rv;
}
/* generic shape gencode routine */
static void shape_gencode(node_t* n)
{
polygon_t *poly;
double xsize, ysize;
int i,j,peripheries,sides,style;
pointf P,*vertices;
static point *A;
static int A_size;
int filled;
poly = (polygon_t*) ND_shape_info(n);
vertices = poly->vertices;
sides = poly->sides;
peripheries = poly->peripheries;
if (A_size < sides) {A_size = sides + 5; A = ALLOC(A_size,A,point);}
CodeGen->begin_node(n);
CodeGen->begin_context();
/* prescale by 16.0 to help rounding trick below */
xsize = ((ND_lw_i(n) + ND_rw_i(n)) / ND_width(n)) * 16.0;
ysize = ((ND_ht_i(n)) / ND_height(n)) * 16.0;
/* this is bad, but it's because of how the VRML driver works */
#ifdef HAVE_LIBPNG
if ((CodeGen == &VRML_CodeGen) && (peripheries == 0)) {
peripheries = 1;
}
#endif
if (ND_shape(n) == point_desc) {
checkStyle (n, &style);
if (style & INVISIBLE) CodeGen->set_style(point_style);
else CodeGen->set_style(&point_style[1]);
style = FILLED;
}
else style = stylenode(n);
pencolor(n); /* emit pen color */
if (style & FILLED) fillcolor(n); /* emit fill color */
for (j = 0; j < peripheries; j++) {
for (i = 0; i < sides; i++) {
P = vertices[i+j*sides];
/* simple rounding produces random results around .5
* this trick should clip off the random part.
* (note xsize/ysize prescaled by 16.0 above) */
A[i].x = ROUND(P.x * xsize) / 16;
A[i].y = ROUND(P.y * ysize) / 16;
if (sides > 2) {A[i].x += ND_coord_i(n).x; A[i].y += ND_coord_i(n).y;}
}
if (!j && (style & FILLED)) {
/* fill innermost periphery only */
filled = 1;
}
else {
filled = 0;
}
if (ND_shape(n)->usershape && CodeGen->user_shape) {
CodeGen->user_shape(ND_shape(n)->name,A,sides,filled);
}
else if (sides <= 2) {
CodeGen->ellipse(ND_coord_i(n),A[0].x,A[0].y,filled);
if (style & DIAGONALS) {
Mcircle_hack(n);
}
}
else if (style & (ROUNDED | DIAGONALS)) {
round_corners(n,A,sides,style);
}
else {
CodeGen->polygon(A,sides,filled);
}
}
if (style & AUXLABELS) Mlabel_hack(n);
ND_label(n)->p = ND_coord_i(n);
emit_label(ND_label(n),n->graph);
CodeGen->end_context();
CodeGen->end_node();
}
#endif
/*=======================end shape======================================*/
/*===============================point start========================*/
/* point_init:
* shorthand for shape=circle, style=filled, width=0.05, label=""
*/
void point_init(node_t* n)
{
textlabel_t* p;
if (!point_desc) {
shape_desc *ptr;
for (ptr = Shapes; ptr->name; ptr++)
if (!strcmp(ptr->name,"point")) {point_desc = ptr; break;}
assert(point_desc);
}
/* adjust label to "" */
p = ND_label(n);
if (p->nlines != 0) free(p->line[0].str);
free(p->line);
free(p->text);
p->nlines = 0;
p->line = 0;
p->text = strdup("");
p->dimen.x = 0;
p->dimen.y = 0;
/* set width and height, and make them equal
* if user has set weight or height, use it.
* if both are set, use smallest.
* if neither, use default
*/
if (ATTR_SET(N_width,n)) {
if (ATTR_SET(N_height,n)) {
ND_width(n) = ND_height(n) = MIN(ND_width(n),ND_height(n));
}
else ND_height(n) = ND_width(n);
}
else if (ATTR_SET(N_height,n)) {
ND_width(n) = ND_height(n);
}
else ND_width(n) = ND_height(n) = DEF_POINT;
poly_init (n);
}
/* the "record" shape is a rudimentary table formatter */
#define HASTEXT 1
#define HASPORT 2
#define HASTABLE 4
#define INTEXT 8
#define INPORT 16
#define ISCTRL(c) ((c) == '{' || (c) == '}' || (c) == '|' || (c) == '<' || (c) == '>')
static char *reclblp;
static field_t *
parse_reclbl(node_t *n, int LR, int flag, char* text)
{
field_t *fp, *rv = NEW(field_t);
char *tsp, *psp, *hstsp, *hspsp, *sp;
char port[SMALLBUF];
int maxf, cnt, mode, wflag, ishardspace, fi;
fp = NULL;
for (maxf = 1, cnt = 0, sp = reclblp; *sp; sp++) {
if (*sp == '\\') {
sp++;
if (*sp && (*sp == '{' || *sp == '}' || *sp == '|'))
continue;
}
if (*sp == '{')
cnt++;
else if (*sp == '}')
cnt--;
else if (*sp == '|' && cnt == 0)
maxf++;
if (cnt < 0)
break;
}
/*maxf = strccnt(reclblp, '|') + 1;*/
rv->fld = N_NEW (maxf, field_t*);
rv->LR = LR;
mode = 0;
fi = 0;
hstsp = tsp = text, hspsp = psp = &port[0];
wflag = TRUE;
ishardspace = FALSE;
while (wflag) {
switch (*reclblp) {
case '<':
if (mode & (HASTABLE | HASPORT))
return NULL;
mode |= (HASPORT | INPORT);
reclblp++;
break;
case '>':
if (!(mode & INPORT))
return NULL;
mode &= ~INPORT;
reclblp++;
break;
case '{':
reclblp++;
if (mode != 0 || !*reclblp)
return NULL;
mode = HASTABLE;
if (!(rv->fld[fi++] = parse_reclbl (n, NOT (LR) , FALSE, text)))
return NULL;
break;
case '}':
case '|':
case '\000':
if ((!*reclblp && !flag) || (mode & INPORT))
return NULL;
if (!(mode & HASTABLE))
fp = rv->fld[fi++] = NEW (field_t);
if (mode & HASPORT) {
if (psp > &port[0] + 1 &&
psp - 1 != hspsp &&
*(psp - 1) == ' ')
psp--;
*psp = '\000';
fp->id = strdup (&port[0]);
hspsp = psp = &port[0];
}
if (!(mode & (HASTEXT | HASTABLE)))
mode |= HASTEXT, *tsp++ = ' ';
if (mode & HASTEXT) {
if (tsp > text + 1 &&
tsp - 1 != hstsp &&
*(tsp - 1) == ' ')
tsp--;
*tsp = '\000';
fp->lp = make_label (strdup (text), ND_label(n)->fontsize, ND_label(n)->fontname, ND_label(n)->fontcolor,n->graph);
fp->LR = TRUE;
hstsp = tsp = text;
}
if (*reclblp) {
if (*reclblp == '}') {
reclblp++;
rv->n_flds = fi;
return rv;
}
mode = 0;
reclblp++;
} else
wflag = FALSE;
break;
case '\\':
if (*(reclblp + 1)) {
if (ISCTRL (*(reclblp + 1)))
reclblp++;
else if (*(reclblp + 1) == ' ')
ishardspace = TRUE, reclblp++;
}
/* falling through ... */
default:
if ((mode & HASTABLE) && *reclblp != ' ')
return NULL;
if (!(mode & (INTEXT | INPORT)) && *reclblp != ' ')
mode |= (INTEXT | HASTEXT);
if (mode & INTEXT) {
if (!(*reclblp == ' ' && !ishardspace &&
*(tsp - 1) == ' '))
*tsp++ = *reclblp;
if (ishardspace)
hstsp = tsp - 1;
} else if (mode & INPORT) {
if (!(*reclblp == ' ' && !ishardspace &&
(psp == &port[0] ||
*(psp - 1) == ' ')))
*psp++ = *reclblp;
if (ishardspace)
hspsp = psp - 1;
}
reclblp++;
break;
}
}
rv->n_flds = fi;
return rv;
}
static point size_reclbl(node_t* n, field_t* f)
{
int i;
point d,d0;
if (f->lp) d = cvt2pt(f->lp->dimen);
else {
d.x = d.y = 0.0;
for (i = 0; i < f->n_flds; i++) {
d0 = size_reclbl(n,f->fld[i]);
if (f->LR) { d.x += d0.x; d.y = MAX(d.y,d0.y); }
else { d.y += d0.y; d.x = MAX(d.x,d0.x); }
}
}
f->size = d;
return d;
}
static void resize_reclbl(field_t* f, point sz)
{
int i,amt;
double inc;
point d,newsz;
field_t *sf;
/* adjust field */
d.x = sz.x - f->size.x; d.y = sz.y - f->size.y;
f->size = sz;
/* adjust children */
if (f->n_flds) {
if (f->LR) inc = (double)d.x/f->n_flds;
else inc = (double)d.y/f->n_flds;
for (i = 0; i < f->n_flds; i++) {
sf = f->fld[i];
amt = ((int)((i+1)*inc)) - ((int)(i*inc));
if (f->LR) newsz = pointof(sf->size.x+amt,sz.y);
else newsz = pointof(sz.x,sf->size.y+amt);
resize_reclbl(sf,newsz);
}
}
}
static void pos_reclbl(field_t* f, point ul)
{
int i;
f->b.LL = pointof(ul.x,ul.y-f->size.y);
f->b.UR = pointof(ul.x+f->size.x,ul.y);
for (i = 0; i < f->n_flds; i++) {
pos_reclbl(f->fld[i],ul);
if (f->LR) ul.x = ul.x + f->fld[i]->size.x;
else ul.y = ul.y - f->fld[i]->size.y;
}
}
/* syntax of labels: foo|bar|baz or foo|(recursive|label)|baz */
void record_init(node_t* n)
{
field_t *info;
point ul,sz;
int len;
char* textbuf; /* temp buffer for storing labels */
reclblp = ND_label(n)->text;
len = strlen (reclblp);
textbuf = N_NEW(len+1,char);
if (!(info = parse_reclbl(n,NOT(GD_left_to_right(n->graph)), TRUE, textbuf))) {
agerr(AGERR, "bad label format %s\n", ND_label(n)->text);
reclblp = "\\N";
info = parse_reclbl(n,NOT(GD_left_to_right(n->graph)), TRUE, textbuf);
}
free (textbuf);
size_reclbl(n,info);
sz.x = POINTS(ND_width(n)); sz.y = POINTS(ND_height(n));
if (mapbool(late_string(n,N_fixed,"false"))) {
if ((sz.x < info->size.x) || (sz.y < info->size.y)) {
/* should check that the record really won't fit, e.g., there may be no text.
agerr(AGWARN, "node '%s' size may be too small\n",
n->name);
*/
}
}
else {
sz.x = MAX(info->size.x,sz.x); sz.y = MAX(info->size.y,sz.y);
}
resize_reclbl(info,sz);
ul = pointof(-sz.x/2,sz.y/2);
pos_reclbl(info,ul);
ND_width(n) = PS2INCH(info->size.x);
ND_height(n) = PS2INCH(info->size.y);
ND_shape_info(n) = (void*) info;
}
static void record_free(node_t* n)
{
field_t* p = ND_shape_info(n);
free(p);
}
static field_t * map_rec_port(field_t* f, char* str)
{
field_t *rv;
int sub;
if (f->id && (strcmp(f->id,str) == 0)) rv = f;
else {
rv = NULL;
for (sub = 0; sub < f->n_flds; sub++)
if ((rv = map_rec_port(f->fld[sub],str))) break;
}
return rv;
}
static port_t record_port(node_t* n, char* pname)
{
field_t *f;
box b;
port_t rv;
if (pname[0] != ':') return Center; /*could be '\000' */
if ((f = map_rec_port((field_t*) ND_shape_info(n),pname+1)) == NULL) {
unrecognized(n,pname);
return Center;
}
b = f->b;
rv.p = pointof((b.LL.x+b.UR.x)/2,(b.LL.y+b.UR.y)/2);
if (GD_left_to_right(n->graph)) rv.p = invflip_pt(rv.p);
rv.order = (MC_SCALE * (ND_lw_i(n) + rv.p.x)) / (ND_lw_i(n) + ND_rw_i(n));
rv.constrained = FALSE;
rv.defined = TRUE;
return rv;
}
static int record_inside(node_t* n, pointf p, edge_t* e)
{
pointf LL,UR;
edge_t *f;
field_t *fld0;
char *pname;
static edge_t *last_e;
static node_t *last_n;
static field_t *fld;
if (GD_left_to_right(n->graph)) p = flip_ptf(p);
for (f = e; ED_edge_type(f) != NORMAL; f = ED_to_orig(f));
e = f;
if ((e != last_e) || (n != last_n)) {
last_e = e; last_n = n;
pname = agget(e,(n == f->head ? "headport" : "tailport"));
fld = map_rec_port((field_t*)ND_shape_info(n),pname+1);
}
if (fld == NULL) {
fld0 = (field_t*) ND_shape_info(n);
UR.x = fld0->size.x / 2.0; LL.x = -UR.x;
UR.y = fld0->size.y / 2.0; LL.y = -UR.y;
}
else {
LL.x = fld->b.LL.x; LL.y = fld->b.LL.y;
UR.x = fld->b.UR.x; UR.y = fld->b.UR.y;
}
return (BETWEEN(LL.x,p.x,UR.x) && BETWEEN(LL.y,p.y,UR.y));
}
static box flip_rec_box(box b, point p)
{
box rv;
/* flip box */
rv.UR.x = b.UR.y; rv.UR.y = b.UR.x;
rv.LL.x = b.LL.y; rv.LL.y = b.LL.x;
/* move box */
rv.LL.x += p.x; rv.LL.y += p.y;
rv.UR.x += p.x; rv.UR.y += p.y;
return rv;
}
static int record_path(node_t* n, edge_t* e, int pt, box rv[], int* kptr)
{
int i,side,ls,rs;
point p;
field_t *info;
if (pt == 1) p = ED_tail_port(e).p;
else p = ED_head_port(e).p;
info = (field_t*) ND_shape_info(n);
for (i = 0; i < info->n_flds; i++) {
if (GD_left_to_right(n->graph) == FALSE)
{ ls = info->fld[i]->b.LL.x; rs = info->fld[i]->b.UR.x; }
else
{ ls = info->fld[i]->b.LL.y; rs = info->fld[i]->b.UR.y; }
if (BETWEEN(ls,p.x,rs)) {
/* FIXME: I don't understand this code */
if (GD_left_to_right(n->graph)) {
rv[0] = flip_rec_box(info->fld[i]->b,ND_coord_i(n));
}
else {
rv[0].LL.x = ND_coord_i(n).x + ls;
rv[0].LL.y = ND_coord_i(n).y - ND_ht_i(n)/2;
rv[0].UR.x = ND_coord_i(n).x + rs;
}
#if 0
s0 = (rv[0].UR.x - rv[0].LL.x)/6;
s0 = MIN(s0,n->GD_nodesep(graph));
s1 = MIN(p.x - rv[0].LL.x,rv[0].UR.x - p.x)/2;
sep = MIN(s0,s1);
rv[0].LL.x += sep;
rv[0].UR.x -= sep;
#endif
rv[0].UR.y = ND_coord_i(n).y + ND_ht_i(n)/2;
*kptr = 1;
break;
}
}
if (pt == 1) side = BOTTOM; else side = TOP;
return side;
}
static void gen_fields(node_t* n, field_t* f)
{
int i;
double cx,cy;
point A[2];
if (f->lp) {
cx = (f->b.LL.x + f->b.UR.x)/2.0 + ND_coord_i(n).x;
cy = (f->b.LL.y + f->b.UR.y)/2.0 + ND_coord_i(n).y;
f->lp->p = pointof((int)cx,(int)cy);
emit_label(f->lp,n->graph);
}
/* yes it is ridiculous that black is hardwired here, the same way
* it is wired into psgen.c ... outline color should be adjustable */
/* for reasons not presently remembered, we used to say
CodeGen->set_color("black");
right here */
for (i = 0; i < f->n_flds; i++) {
if (i > 0) {
if (f->LR) {
A[0] = f->fld[i]->b.LL;
A[1].x = A[0].x;
A[1].y = f->fld[i]->b.UR.y;
}
else {
A[1] = f->fld[i]->b.UR;
A[0].x = f->fld[i]->b.LL.x;
A[0].y = A[1].y;
}
A[0] = add_points(A[0],ND_coord_i(n));
A[1] = add_points(A[1],ND_coord_i(n));
CodeGen->polyline(A,2);
}
gen_fields(n,f->fld[i]);
}
}
static void record_gencode(node_t* n)
{
point A[4];
int i,style;
field_t *f;
f = (field_t*) ND_shape_info(n);
A[0] = f->b.LL;
A[2] = f->b.UR;
A[1].x = A[2].x; A[1].y = A[0].y;
A[3].x = A[0].x; A[3].y = A[2].y;
for (i = 0; i < 4; i++) A[i] = add_points(A[i],ND_coord_i(n));
CodeGen->begin_node(n);
CodeGen->begin_context();
style = stylenode(n);
pencolor(n);
if (style & FILLED) fillcolor(n); /* emit fill color */
if (streq(ND_shape(n)->name,"Mrecord")) style |= ROUNDED;
if (style & (ROUNDED | DIAGONALS)) round_corners(n,A,4,ROUNDED);
else CodeGen->polygon(A,4,style&FILLED);
gen_fields(n,f);
CodeGen->end_context();
CodeGen->end_node();
}
static shape_desc **UserShape;
static int N_UserShape;
shape_desc *find_user_shape(char* name)
{
int i;
if (UserShape) {
for (i = 0; i < N_UserShape; i++) {
if (streq(UserShape[i]->name,name)) return UserShape[i];
}
}
return NULL;
}
static shape_desc *user_shape(char* name)
{
int i;
shape_desc *p;
if ((p = find_user_shape(name))) return p;
i = N_UserShape++;
UserShape = ALLOC(N_UserShape,UserShape,shape_desc*);
p = UserShape[i] = NEW(shape_desc);
*p = Shapes[0];
p->name = name;
p->usershape = TRUE;
if ((Lib == NULL) && (!CodeGen || (CodeGen->usershapesize == NULL)))
agerr(AGWARN, "using %s for unknown shape %s\n",
Shapes[0].name,p->name);
return p;
}
shape_desc * bind_shape(char* name)
{
shape_desc *ptr,*rv= NULL;
for (ptr = Shapes; ptr->name; ptr++)
if (!strcmp(ptr->name,name)) {rv = ptr; break;}
if (rv == NULL) rv = user_shape(name);
return rv;
}
static int epsf_inside(node_t* n, pointf p, edge_t* e)
{
pointf P;
double x2;
P = (GD_left_to_right(n->graph)? flip_ptf(p) : p);
x2 = ND_ht_i(n) / 2;
return ((P.y >= -x2) && (P.y <= x2) && (P.x >= -ND_lw_i(n)) && (P.x <= ND_rw_i(n)));
}
|