From: Emden Gansner Date: Fri, 6 Jul 2012 17:03:36 +0000 (-0400) Subject: Refine striped boxes to handle user-supplied stripe sizes; X-Git-Tag: LAST_LIBGRAPH~32^2~380 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=29b7225ef9a8ae04106fc1dea4388ffad00572d0;p=graphviz Refine striped boxes to handle user-supplied stripe sizes; rely on color list parser used for multicolor edges, which is modified to gather sizes summing to 1 rather than a monotonic sequence ending at 1. In addition, use semicolor rather than comma as separator to allow HSV color values. --- diff --git a/lib/common/emit.c b/lib/common/emit.c index b1390b2a1..f74572c5c 100644 --- a/lib/common/emit.c +++ b/lib/common/emit.c @@ -315,71 +315,185 @@ static char **checkClusterStyle(graph_t* sg, int *flagp) return pstyle; } +typedef struct { + char* color; /* segment color */ + float t; /* segment size >= 0 */ +} colorseg_t; +/* Sum of segment sizes should add to 1 */ +typedef struct { + int numc; /* number of used segments in segs */ + char* base; /* storage of color names */ + colorseg_t* segs; /* array of segments */ +} colorsegs_t; + +static void +freeSegs (colorsegs_t* segs) +{ + free (segs->base); + free (segs->segs); + free (segs); +} + +/* getSegLen: + * Find semicolon in s, replace with '\0'. + * Convert remainder to float v. + * Return 0 if no float given + * Return -1 on failure + */ +static double getSegLen (char* s) +{ + char* p = strchr (s, ';'); + char* endp; + double v; + + if (!p) { + return 0; + } + *p++ = '\0'; + v = strtod (p, &endp); + if (endp != p) { /* scanned something */ + if (v >= 0) + return v; + } + return -1; +} + +/* parseSegs: + * Parse string of form color;float:color;float:...:color;float:color + * where the floats are optional, nonnegative, sum to <= 1. + * Store the values in an array of colorseg_t's and return the array in psegs. + * If nseg == 0, count the number of colors. + * 0 => okay + * 1 => error without message + * 2 => error with message + * 3 => warning message + * There is a last sentinel segment with color == NULL; it will always follow + * the last segment with t > 0. + */ static int -splitColorList (char* clist, char*** carray) +parseSegs (char* clrs, int nseg, colorsegs_t** psegs) { + colorsegs_t* segs = NEW(colorsegs_t); + colorseg_t* s; + char* colors = strdup (clrs); + char* color; + int cnum = 0; + double v, left = 1; + static int doWarn = 1; + int i, rval = 0; char* p; - char** colors; - char* buf; - int i, numc = 1; - /* count colors */ - for (p = clist; *p; p++) { - if (*p == ':') numc++; + if (nseg == 0) { + nseg = 1; + /* need to know how many colors separated by ':' */ + for (p = colors; *p; p++) { + if (*p == ':') nseg++; + } } - buf = strdup(clist); - colors = N_NEW(numc, char*); - - for (i = 0, p = strtok(buf, ":"); p; i++, p = strtok(0, ":")) { - if (p[0] == '\0') - colors[i] = NULL; - else - colors[i] = p; + + segs->base = colors; + segs->segs = s = N_NEW(nseg+1,colorseg_t); + for (color = strtok(colors, ":"); color; color = strtok(0, ":")) { + if ((v = getSegLen (color)) >= 0) { + if (v > left) { + if (doWarn) { + agerr (AGWARN, "Total size > 1 in \"%s\" color spec ", clrs); + doWarn = 0; + rval = 3; + } + v = left; + } + left -= v; + s[cnum].color = color; + s[cnum++].t = v; + } + else { + if (doWarn) { + agerr (AGERR, "Illegal length value in \"%s\" color attribute ", clrs); + doWarn = 0; + rval = 2; + } + else rval = 1; + freeSegs (segs); + return rval; + } } - *carray = colors; - return numc; + /* distribute remaining into slot with t == 0; if none, add to last */ + if (left > 0) { + /* count zero segments */ + nseg = 0; + for (i = 0; i < cnum; i++) { + if (s[i].t == 0) nseg++; + } + if (nseg > 0) { + double delta = left/nseg; + for (i = 0; i < cnum; i++) { + if (s[i].t == 0) s[i].t = delta; + } + } + else { + s[cnum-1].t += left; + } + } + + /* Make sure last positive segment is followed by a sentinel. */ + nseg = 0; + for (i = cnum-1; i >= 0; i--) { + if (s[i].t > 0) break; + } + s[i+1].color = NULL; + segs->numc = i+1; + + *psegs = segs; + return rval; } /* stripedBox: * Fill a rectangular box with vertical stripes of colors. * AF gives 4 corner points, with AF[0] the LL corner and the points ordered CCW. - * clrs is a list of colon separated colors. - * No boundaries are drawn. + * clrs is a list of colon separated colors, with possible quantities. + * Thin boundaries are drawn. + * 0 => okay + * 1 => error without message + * 2 => error with message + * 3 => warning message */ -void +int stripedBox (GVJ_t * job, pointf* AF, char* clrs) { - char** colors; - int i, numclrs = splitColorList (clrs, &colors); + colorsegs_t* segs; + colorseg_t* s; + int rv; double xdelta; pointf pts[4]; double lastx = AF[1].x; int save_penwidth = job->obj->penwidth; - if (numclrs < 1) numclrs = 1; - xdelta = (AF[1].x - AF[0].x)/numclrs; + rv = parseSegs (clrs, 0, &segs); + if ((rv == 1) || (rv == 2)) return rv; + xdelta = (AF[1].x - AF[0].x); pts[0] = AF[0]; pts[1] = AF[1]; pts[2] = AF[2]; pts[3] = AF[3]; - if (numclrs > 1) - pts[1].x = pts[2].x = pts[0].x + xdelta; + pts[1].x = pts[2].x = pts[0].x; gvrender_set_penwidth(job, 0.5); - for (i = 0; i < numclrs; i++) { - gvrender_set_fillcolor (job, (colors[i]?colors[i]:DEFAULT_COLOR)); + for (s = segs->segs; s->color; s++) { + if (s->t == 0) continue; + gvrender_set_fillcolor (job, (s->color?s->color:DEFAULT_COLOR)); /* gvrender_polygon(job, pts, 4, FILL | NO_POLY); */ - gvrender_polygon(job, pts, 4, FILL); - pts[0].x = pts[3].x = pts[1].x; - if (i == numclrs-2) + if (s[1].color == NULL) pts[1].x = pts[2].x = lastx; else - pts[1].x = pts[2].x = pts[0].x + xdelta; + pts[1].x = pts[2].x = pts[0].x + xdelta*(s->t); + gvrender_polygon(job, pts, 4, FILL); + pts[0].x = pts[3].x = pts[1].x; } gvrender_set_penwidth(job, save_penwidth); - free (colors[0]); - free (colors); + freeSegs (segs); + return rv; } void emit_map_rect(GVJ_t *job, boxf b) @@ -1759,118 +1873,6 @@ static char* default_pencolor(char *pencolor, char *deflt) return buf; } -typedef struct { - char* color; - float t; -} colorseg_t; -typedef struct { - char* base; - colorseg_t* segs; -} colorsegs_t; - -static void -freeSegs (colorsegs_t* segs) -{ - free (segs->base); - free (segs->segs); - free (segs); -} - -/* getSegLen: - * Find comma in s, replace with '\0'. - * Convert remainder to float v and verify prev_v <= v <= 1. - * Return -1 on failure - */ -static double getSegLen (char* s, double prev_v) -{ - char* p = strchr (s, ','); - char* endp; - double v; - - if (!p) { - agerr (AGERR, "No comma in color spec \"%s\" in color attribute ", s); - return -1; - } - *p++ = '\0'; - v = strtod (p, &endp); - if (endp != p) { /* scanned something */ - if ((prev_v <= v) && (v <= 1)) - return v; - } - return -1; -} - -/* parseSegs: - * Parse string of form color,float:color,float:...:color,float:color - * where the floats are nonnegative, <= 1, and increasing. - * We allow equality, discarding the later values. - * Store the values in an array of colorseg_t's and return the array in psegs. - * 0 => okay - * 1 => error without message - * 2 => error with message - * 3 => warning message - */ -static int -parseSegs (char* clrs, int nseg, colorsegs_t** psegs) -{ - colorsegs_t* segs = NEW(colorsegs_t); - colorseg_t* s = N_NEW(nseg+1,colorseg_t); - char* colors = strdup (clrs); - char* color; - int cnum = 0; - double v, prev_v = 0; - static int doWarn = 1; - int rval = 0; - - segs->base = colors; - segs->segs = s = N_NEW(nseg+1,colorseg_t); - for (color = strtok(colors, ":"); color; color = strtok(0, ":")) { - if (cnum == nseg-1) { - if (prev_v < 1) { - char* p = strchr (color, ','); - if (p) /* mask optional length */ - *p = '\0'; - s[cnum].color = color; - s[cnum++].t = 1.0; - } - } - else if ((v = getSegLen (color, prev_v)) >= 0) { - if (prev_v < v) { - s[cnum].color = color; - s[cnum++].t = (v - prev_v)/(1.0 - prev_v); - prev_v = v; - } - else { - nseg--; - if (doWarn) { - agerr (AGWARN, "0-length in color spec \"%s\"\n", clrs); - doWarn = 0; - rval = 3; - } - } - } - else { - if (doWarn) { - agerr (AGERR, "Illegal length value in \"%s\" color attribute ", clrs); - doWarn = 0; - rval = 2; - } - else rval = 1; - freeSegs (segs); - return rval; - } - } - - if (cnum == 0) { - freeSegs (segs); - segs = NULL; - rval = 1; - } - else - *psegs = segs; - return rval; -} - /* approxLen: */ static double approxLen (pointf* pts) @@ -1954,6 +1956,7 @@ static int multicolor (GVJ_t * job, edge_t * e, char** styles, char* colors, int colorsegs_t* segs; colorseg_t* s; char* endcolor; + double sum; rv = parseSegs (colors, num, &segs); if (rv > 1) { @@ -1973,17 +1976,20 @@ static int multicolor (GVJ_t * job, edge_t * e, char** styles, char* colors, int for (i = 0; i < ED_spl(e)->size; i++) { + sum = 0; bz = ED_spl(e)->list[i]; for (s = segs->segs; s->color; s++) { + if (s->t == 0) continue; gvrender_set_pencolor(job, s->color); + sum += s->t; if (s == segs->segs) { - splitBSpline (&bz, s->t, &bz_l, &bz_r); + splitBSpline (&bz, sum, &bz_l, &bz_r); gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE); free (bz_l.list); } - else if (s->t < 1.0) { + else if (sum < 1.0) { bz0 = bz_r; - splitBSpline (&bz0, s->t, &bz_l, &bz_r); + splitBSpline (&bz0, sum, &bz_l, &bz_r); free (bz0.list); gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE); free (bz_l.list); @@ -2067,7 +2073,7 @@ taperfun (edge_t* e) static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles) { - int i, j, cnum, numc = 0, numcomma = 0; + int i, j, cnum, numc = 0, numsemi = 0; char *color, *pencolor, *fillcolor; char *headcolor, *tailcolor, *lastcolor; char *colors = NULL; @@ -2099,11 +2105,11 @@ static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles) for (p = color; *p; p++) { if (*p == ':') numc++; - else if (*p == ',') - numcomma++; + else if (*p == ';') + numsemi++; } - if (numcomma && numc) { + if (numsemi && numc) { if (multicolor (job, e, styles, color, numc+1, arrowsize, penwidth)) { color = DEFAULT_COLOR; } @@ -3537,6 +3543,7 @@ void emit_clusters(GVJ_t * job, Agraph_t * g, int flags) } } else if (istyle & STRIPED) { + int rv; AF[0] = GD_bb(sg).LL; AF[2] = GD_bb(sg).UR; AF[1].x = AF[2].x; @@ -3547,7 +3554,9 @@ void emit_clusters(GVJ_t * job, Agraph_t * g, int flags) gvrender_set_pencolor(job, "transparent"); else gvrender_set_pencolor(job, pencolor); - stripedBox (job, AF, fillcolor); + rv = stripedBox (job, AF, fillcolor); + if (rv > 1) + agerr (AGPREV, "in cluster %s\n", agnameof(sg)); gvrender_box(job, GD_bb(sg), 0); } else { diff --git a/lib/common/render.h b/lib/common/render.h index d56bc32e3..e5ffd6ed4 100644 --- a/lib/common/render.h +++ b/lib/common/render.h @@ -152,7 +152,7 @@ extern "C" { extern shape_kind shapeOf(node_t *); extern void shape_clip(node_t * n, pointf curve[4]); extern void make_simple_label (graph_t* g, textlabel_t* rv); - extern void stripedBox (GVJ_t * job, pointf* AF, char* clrs); + extern int stripedBox (GVJ_t * job, pointf* AF, char* clrs); extern stroke_t* taper (bezier*, double (*radfunc_t)(double,double,double), double initwid, int linejoin, int linecap); extern stroke_t* taper0 (bezier* bez, double initwid); extern pointf textsize(graph_t *g, textpara_t * para, char *fontname, double fontsize); diff --git a/lib/common/shapes.c b/lib/common/shapes.c index 6d5d659dd..71ff16bfd 100644 --- a/lib/common/shapes.c +++ b/lib/common/shapes.c @@ -1727,8 +1727,16 @@ static void poly_gencode(GVJ_t * job, node_t * n) Mcircle_hack(job, n); } } else if (style & STRIPED) { - if (j == 0) - stripedBox (job, AF, fillcolor); + if (j == 0) { + pointf af[4]; + af[0] = AF[2]; + af[1] = AF[3]; + af[2] = AF[0]; + af[3] = AF[1]; + int rv = stripedBox (job, af, fillcolor); + if (rv > 1) + agerr (AGPREV, "in node %s\n", agnameof(n)); + } gvrender_polygon(job, AF, sides, 0); } else if (SPECIAL_CORNERS(style)) { round_corners(job, AF, sides, style, filled);