From bd715d227c39077d697017e111919e9406833972 Mon Sep 17 00:00:00 2001 From: oetiker Date: Tue, 26 Mar 2002 07:02:28 +0000 Subject: [PATCH] added SVG support -- Peter Speck git-svn-id: svn://svn.oetiker.ch/rrdtool/trunk/program@106 a5681a0c-68f1-0310-ab6d-d61299d08faa --- doc/rrdgraph.src | 6 +- src/rrd_gfx.c | 402 +++++++++++++++++++++++++++++++++++++++++++++++ src/rrd_gfx.h | 6 + src/rrd_graph.h | 2 +- 4 files changed, 412 insertions(+), 4 deletions(-) diff --git a/doc/rrdgraph.src b/doc/rrdgraph.src index 06e0761..9dc4050 100644 --- a/doc/rrdgraph.src +++ b/doc/rrdgraph.src @@ -61,7 +61,7 @@ statements (after all, most of them do define graph elements...). =item filename The name and path of the graph to generate. It is recommended to -end this in C<.png> or C<.gif> but B does not enforce this. +end this in C<.png> or C<.svg> but B does not enforce this. I can be 'C<->' to send the image to C. In that case, no other output is generated. @@ -300,9 +300,9 @@ for the graph legend. Use Times for the title: C<--font TITLE:13:/usr/lib/fonts/times.ttf> -B<[-a|--imgformat C]> +B<[-a|--imgformat C|C]> -At present only PNG is supported, but C, C or even C should +At present only C and C are supported, but C, or even C should not be all that difficult to add. B<[-i|--interlaced]> diff --git a/src/rrd_gfx.c b/src/rrd_gfx.c index 405291b..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; @@ -173,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; @@ -467,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; + } diff --git a/src/rrd_gfx.h b/src/rrd_gfx.h index 808b7c4..504f425 100644 --- a/src/rrd_gfx.h +++ b/src/rrd_gfx.h @@ -25,6 +25,7 @@ typedef struct gfx_node_t { char *filename; /* font or image filename */ char *text; double x,y; /* position */ + double angle; enum gfx_h_align_en halign; /* text alignement */ enum gfx_v_align_en valign; /* text alignement */ double tabwidth; @@ -86,5 +87,10 @@ int gfx_destroy (gfx_canvas_t *canvas); #endif +/* turn graph into an svg image */ +int gfx_render_svg (gfx_canvas_t *canvas, + art_u32 width, art_u32 height, + double zoom, + gfx_color_t background, FILE *fo); diff --git a/src/rrd_graph.h b/src/rrd_graph.h index d222f9a..8fb96d6 100644 --- a/src/rrd_graph.h +++ b/src/rrd_graph.h @@ -25,7 +25,7 @@ enum gf_en {GF_PRINT=0,GF_GPRINT,GF_COMMENT,GF_HRULE,GF_VRULE,GF_LINE, GF_DEF, GF_CDEF, GF_VDEF, GF_PART}; -enum if_en {IF_PNG=0}; +enum if_en {IF_PNG=0,IF_SVG}; enum vdef_op_en { VDEF_MAXIMUM /* like the MAX in (G)PRINT */ -- 2.30.2