summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 80fc2a6)
raw | patch | inline | side by side (parent: 80fc2a6)
author | oetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa> | |
Tue, 26 Mar 2002 07:02:28 +0000 (07:02 +0000) | ||
committer | oetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa> | |
Tue, 26 Mar 2002 07:02:28 +0000 (07:02 +0000) |
program/doc/rrdgraph.src | patch | blob | history | |
program/src/rrd_gfx.c | patch | blob | history | |
program/src/rrd_gfx.h | patch | blob | history | |
program/src/rrd_graph.h | patch | blob | history |
index 06e07615ea64ab1da053eed4924690653a6fccb3..9dc40507f9b362a3209203ff8756c4927bf60e72 100644 (file)
--- a/program/doc/rrdgraph.src
+++ b/program/doc/rrdgraph.src
=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<rrdtool> does not enforce this.
+end this in C<.png> or C<.svg> but B<rrdtool> does not enforce this.
I<filename> can be 'C<->' to send the image to C<stdout>. In
that case, no other output is generated.
Use Times for the title: C<--font TITLE:13:/usr/lib/fonts/times.ttf>
-B<[-a|--imgformat C<PNG>]>
+B<[-a|--imgformat C<PNG>|C<SVG>]>
-At present only PNG is supported, but C<EPS>, C<SVG> or even C<FLASH> should
+At present only C<PNG> and C<SVG> are supported, but C<EPS>, or even C<FLASH> should
not be all that difficult to add.
B<[-i|--interlaced]>
diff --git a/program/src/rrd_gfx.c b/program/src/rrd_gfx.c
index 405291b345f3303a7c91790885c4ea07b81f6179..e36910de8612529fee2820e218a154b365d46c44 100644 (file)
--- a/program/src/rrd_gfx.c
+++ b/program/src/rrd_gfx.c
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;
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("</", fp);
+ fputs(name, fp);
+ } else {
+ putc('/', fp);
+ }
+ svg_close_tag(fp);
+ }
+
+ static void svg_close_tag_empty_node(FILE *fp)
+ {
+ svg_end_tag(fp, NULL);
+ }
+
+ static void svg_write_text(FILE *fp, const char *p)
+ {
+ char ch;
+ const char *start, *last;
+ if (!p)
+ return;
+ /* trim leading spaces */
+ while (*p == ' ')
+ p++;
+ start = p;
+ /* trim trailing spaces */
+ last = p - 1;
+ while ((ch = *p) != 0) {
+ if (ch != ' ')
+ last = p;
+ p++;
+ }
+ /* encode trimmed text */
+ p = start;
+ while (p <= last) {
+ ch = *p++;
+ switch (ch) {
+ case '&': fputs("&", fp); break;
+ case '<': fputs("<", fp); break;
+ case '>': 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 <path..> tags take less space than
+ <line..> 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(
+ "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"
+ " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
+ "<!--\n"
+ " SVG file created by RRDtool,\n"
+ " Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch\n"
+ "\n"
+ " The width/height attributes in the outhermost svg node\n"
+ " are just default sizes for the browser which is used\n"
+ " if the svg file is openened directly without being\n"
+ " embedded in an html file.\n"
+ " The viewBox is the local coord system for rrdtool.\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/program/src/rrd_gfx.h b/program/src/rrd_gfx.h
index 808b7c40a84a9545c44558d1ebd5077ad2e9f085..504f425117ce11f15d06f18798aab88171ff5451 100644 (file)
--- a/program/src/rrd_gfx.h
+++ b/program/src/rrd_gfx.h
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;
#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);
index d222f9ae5d5e16ba05a64241e5ab1b1a0a1896b3..8fb96d6844554a245b5c2ca37bc687165dd84dfe 100644 (file)
--- a/program/src/rrd_graph.h
+++ b/program/src/rrd_graph.h
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 */