X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=src%2Frrd_gfx.c;h=e36910de8612529fee2820e218a154b365d46c44;hb=bd715d227c39077d697017e111919e9406833972;hp=1a45439da3d0eb542967f9499cccee785962fb0b;hpb=b7ba62419191213693d0b47c441d65d3ef88a685;p=rrdtool.git diff --git a/src/rrd_gfx.c b/src/rrd_gfx.c index 1a45439..e36910d 100644 --- a/src/rrd_gfx.c +++ b/src/rrd_gfx.c @@ -37,6 +37,7 @@ gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){ node->text = NULL; node->x = 0.0; node->y = 0.0; /* position */ + node->angle = 0; node->halign = GFX_H_NULL; /* text alignement */ node->valign = GFX_V_NULL; /* text alignement */ node->tabwidth = 0.0; @@ -108,52 +109,6 @@ gfx_node_t *gfx_new_area (gfx_canvas_t *canvas, return node; } -/* create an arc section (2*M_PI is full circle) */ -gfx_node_t *gfx_arc_sect (gfx_canvas_t *canvas, - double centerx, double centery, - double radiusx, double radiusy, - double start, double end, - gfx_color_t color) { - - gfx_node_t *node; - ArtVpath *vec; - int counter; - double position; - -/* 20 is too low, 100 is overkill */ -#define AMOUNT_OF_VECTORS 50 - - node = gfx_new_node(canvas,GFX_AREA); - if (node == NULL) return NULL; - vec = art_new(ArtVpath, AMOUNT_OF_VECTORS+4); - if (vec == NULL) return NULL; - - vec[0].code = ART_MOVETO; - vec[0].x = centerx; - vec[0].y = centery; - - for (counter=0;counter<=AMOUNT_OF_VECTORS;) { - position=start + counter*(end-start)/AMOUNT_OF_VECTORS; - - counter++; - vec[counter].code = ART_LINETO; - vec[counter].x = centerx + sin(position)*radiusx; - vec[counter].y = centery - cos(position)*radiusy; - } - - vec[AMOUNT_OF_VECTORS+2].code = ART_LINETO; - vec[AMOUNT_OF_VECTORS+2].x = centerx; - vec[AMOUNT_OF_VECTORS+2].y = centery; - - vec[AMOUNT_OF_VECTORS+3].code = ART_END; - - node->points = AMOUNT_OF_VECTORS+4; - node->points_max = AMOUNT_OF_VECTORS+4; - node->color = color; - node->path = vec; - - return node; -} /* add a point to a line or to an area */ int gfx_add_point (gfx_node_t *node, @@ -219,6 +174,7 @@ gfx_node_t *gfx_new_text (gfx_canvas_t *canvas, node->filename = strdup(font); node->x = x; node->y = y; + node->angle = angle; node->color = color; node->tabwidth = tabwidth; node->halign = h_align; @@ -513,3 +469,403 @@ static int gfx_save_png (art_u8 *buffer, FILE *fp, long width, long height, lon png_destroy_write_struct(&png_ptr, &info_ptr); return 1; } + + +static int svg_indent = 0; +static int svg_single_line = 0; +static void svg_print_indent(FILE *fp) +{ + int i; + for (i = svg_indent - svg_single_line; i > 0; i--) { + putc(' ', fp); + putc(' ', fp); + } +} + +static void svg_start_tag(FILE *fp, const char *name) + { + svg_print_indent(fp); + putc('<', fp); + fputs(name, fp); + svg_indent++; + } + + static void svg_close_tag_single_line(FILE *fp) + { + svg_single_line++; + putc('>', fp); + } + + static void svg_close_tag(FILE *fp) + { + putc('>', fp); + if (!svg_single_line) + putc('\n', fp); + } + + static void svg_end_tag(FILE *fp, const char *name) + { + /* name is NULL if closing empty-node tag */ + svg_indent--; + if (svg_single_line) + svg_single_line--; + else if (name) + svg_print_indent(fp); + if (name != NULL) { + fputs("': fputs(">", fp); break; + case '"': fputs(""", fp); break; + default: putc(ch, fp); + } + } + } + + static void svg_write_number(FILE *fp, double d) + { + /* omit decimals if integer to reduce filesize */ + char buf[60], *p; + snprintf(buf, sizeof(buf), "%.2f", d); + p = buf; /* doesn't trust snprintf return value */ + while (*p) + p++; + while (--p > buf) { + char ch = *p; + if (ch == '0') { + *p = '\0'; /* zap trailing zeros */ + continue; + } + if (ch == '.') + *p = '\0'; /* zap trailing dot */ + break; + } + fputs(buf, fp); + } + + static int svg_color_is_black(int c) + { + /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB like html */ + c = (int)((c >> 8) & 0xFFFFFF); + return !c; + } + + static void svg_write_color(FILE *fp, int c) + { + /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB like html */ + c = (int)((c >> 8) & 0xFFFFFF); + if ((c & 0x0F0F0F) == ((c >> 4) & 0x0F0F0F)) { + /* css2 short form, #rgb is #rrggbb, not #r0g0b0 */ + fprintf(fp, "#%03X", + ( ((c >> 8) & 0xF00) + | ((c >> 4) & 0x0F0) + | ( c & 0x00F))); + } else { + fprintf(fp, "#%06X", c); + } + } + + static int svg_is_int_step(double a, double b) + { + double diff = fabs(a - b); + return floor(diff) == diff; + } + + static int svg_path_straight_segment(FILE *fp, + double lastA, double currentA, double currentB, + gfx_node_t *node, + int segment_idx, int isx, char absChar, char relChar) + { + if (!svg_is_int_step(lastA, currentA)) { + putc(absChar, fp); + svg_write_number(fp, currentA); + return 0; + } + if (segment_idx < node->points - 1) { + ArtVpath *vec = node->path + segment_idx + 1; + if (vec->code == ART_LINETO) { + double nextA = (isx ? vec->x : vec->y) - LINEOFFSET; + double nextB = (isx ? vec->y : vec->x) - LINEOFFSET; + if (nextB == currentB + && ((currentA >= lastA) == (nextA >= currentA)) + && svg_is_int_step(currentA, nextA)) { + return 1; /* skip to next as it is a straight line */ + } + } + } + putc(relChar, fp); + svg_write_number(fp, currentA - lastA); + return 0; + } + + static void svg_path(FILE *fp, gfx_node_t *node, int multi) + { + int i; + double lastX = 0, lastY = 0; + /* for straight lines tags take less space than + tags because of the efficient packing + in the 'd' attribute */ + svg_start_tag(fp, "path"); + if (!multi) { + fputs(" stroke-width=\"", fp); + svg_write_number(fp, node->size); + fputs("\" stroke=\"", fp); + svg_write_color(fp, node->color); + fputs("\" fill=\"none\"", fp); + } + fputs(" d=\"", fp); + /* specification of the 'd' attribute: */ + /* http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation */ + for (i = 0; i < node->points; i++) { + ArtVpath *vec = node->path + i; + double x = vec->x - LINEOFFSET; + double y = vec->y - LINEOFFSET; + switch (vec->code) { + case ART_MOVETO_OPEN: /* fall-through */ + case ART_MOVETO: + putc('M', fp); + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + break; + case ART_LINETO: + /* try optimize filesize by using minimal lineto commands */ + /* without introducing rounding errors. */ + if (x == lastX) { + if (svg_path_straight_segment(fp, lastY, y, x, node, i, 0, 'V', 'v')) + continue; + } else if (y == lastY) { + if (svg_path_straight_segment(fp, lastX, x, y, node, i, 1, 'H', 'h')) + continue; + } else { + putc('L', fp); + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + } + break; + case ART_CURVETO: break; /* unsupported */ + case ART_END: break; /* nop */ + } + lastX = x; + lastY = y; + } + fputs("\"", fp); + svg_close_tag_empty_node(fp); + } + + static void svg_multi_path(FILE *fp, gfx_node_t **nodeR) + { + /* optimize for multiple paths with the same color, penwidth, etc. */ + int num = 1; + gfx_node_t *node = *nodeR; + gfx_node_t *next = node->next; + while (next) { + if (next->type != node->type + || next->size != node->size + || next->color != node->color) + break; + next = next->next; + num++; + } + if (num == 1) { + svg_path(fp, node, 0); + return; + } + svg_start_tag(fp, "g"); + fputs(" stroke-width=\"", fp); + svg_write_number(fp, node->size); + fputs("\" stroke=\"", fp); + svg_write_color(fp, node->color); + fputs("\" fill=\"none\"", fp); + svg_close_tag(fp); + while (num && node) { + svg_path(fp, node, 1); + if (!--num) + break; + node = node->next; + *nodeR = node; + } + svg_end_tag(fp, "g"); + } + + static void svg_area(FILE *fp, gfx_node_t *node) + { + int i; + double startX = 0, startY = 0; + svg_start_tag(fp, "polygon"); + fputs(" fill=\"", fp); + svg_write_color(fp, node->color); + fputs("\" points=\"", fp); + for (i = 0; i < node->points; i++) { + ArtVpath *vec = node->path + i; + double x = vec->x - LINEOFFSET; + double y = vec->y - LINEOFFSET; + switch (vec->code) { + case ART_MOVETO_OPEN: /* fall-through */ + case ART_MOVETO: + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + startX = x; + startY = y; + break; + case ART_LINETO: + if (i == node->points - 2 + && node->path[i + 1].code == ART_END + && fabs(x - startX) < 0.001 && fabs(y - startY) < 0.001) { + break; /* poly area always closed, no need for last point */ + } + putc(' ', fp); + svg_write_number(fp, x); + putc(',', fp); + svg_write_number(fp, y); + break; + case ART_CURVETO: break; /* unsupported */ + case ART_END: break; /* nop */ + } + } + fputs("\"", fp); + svg_close_tag_empty_node(fp); + } + + static void svg_text(FILE *fp, gfx_node_t *node) + { + double x = node->x - LINEOFFSET; + double y = node->y - LINEOFFSET; + if (node->angle != 0) { + svg_start_tag(fp, "g"); + fputs(" transform=\"translate(", fp); + svg_write_number(fp, x); + fputs(",", fp); + svg_write_number(fp, y); + fputs(") rotate(", fp); + svg_write_number(fp, node->angle); + fputs(")\"", fp); + x = y = 0; + svg_close_tag(fp); + } + switch (node->valign) { + case GFX_V_TOP: y += node->size; break; + case GFX_V_CENTER: y += node->size / 2; break; + case GFX_V_BOTTOM: break; + case GFX_V_NULL: break; + } + svg_start_tag(fp, "text"); + fputs(" x=\"", fp); + svg_write_number(fp, x); + fputs("\" y=\"", fp); + svg_write_number(fp, y); + fputs("\" font-size=\"", fp); + svg_write_number(fp, node->size); + fputs("\"", fp); + if (!svg_color_is_black(node->color)) { + fputs(" fill=\"", fp); + svg_write_color(fp, node->color); + fputs("\"", fp); + } + switch (node->halign) { + case GFX_H_RIGHT: fputs(" text-anchor=\"end\"", fp); break; + case GFX_H_CENTER: fputs(" text-anchor=\"middle\"", fp); break; + case GFX_H_LEFT: break; + case GFX_H_NULL: break; + } + svg_close_tag_single_line(fp); + /* support for node->tabwidth missing */ + svg_write_text(fp, node->text); + svg_end_tag(fp, "text"); + if (node->angle != 0) + svg_end_tag(fp, "g"); + } + + int gfx_render_svg (gfx_canvas_t *canvas, + art_u32 width, art_u32 height, + double zoom, + gfx_color_t background, FILE *fp){ + gfx_node_t *node = canvas->firstnode; + fputs( + "\n" + "\n" + "\n", fp); + svg_start_tag(fp, "svg"); + fputs(" width=\"", fp); + svg_write_number(fp, width * zoom); + fputs("\" height=\"", fp); + svg_write_number(fp, height * zoom); + fputs("\" x=\"0\" y=\"0\" viewBox=\"", fp); + svg_write_number(fp, -LINEOFFSET); + fputs(" ", fp); + svg_write_number(fp, -LINEOFFSET); + fputs(" ", fp); + svg_write_number(fp, width - LINEOFFSET); + fputs(" ", fp); + svg_write_number(fp, height - LINEOFFSET); + fputs("\" preserveAspectRatio=\"xMidYMid\"", fp); + fputs(" font-family=\"Helvetica\"", fp); /* default font */ + svg_close_tag(fp); + svg_start_tag(fp, "rect"); + fprintf(fp, " x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", width, height); + fputs(" style=\"fill:", fp); + svg_write_color(fp, background); + fputs("\"", fp); + svg_close_tag_empty_node(fp); + while (node) { + switch (node->type) { + case GFX_LINE: + svg_multi_path(fp, &node); + break; + case GFX_AREA: + svg_area(fp, node); + break; + case GFX_TEXT: + svg_text(fp, node); + } + node = node->next; + } + svg_end_tag(fp, "svg"); + return 0; + }