-
-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;
- }