added SVG support -- Peter Speck <speck@ruc.dk>
[rrdtool.git] / src / rrd_gfx.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd_gfx.c  graphics wrapper for rrdtool
5   **************************************************************************/
6
7 /* #define DEBUG */
8
9 #ifdef DEBUG
10 # define DPRINT(x)    (void)(printf x, printf("\n"))
11 #else
12 # define DPRINT(x)
13 #endif
14
15 #include <png.h>
16 #include <ft2build.h>
17 #include FT_FREETYPE_H
18 #include <math.h>
19
20 #include "rrd_gfx.h"
21
22 /* lines are better drawn on the pixle than between pixles */
23 #define LINEOFFSET 0.5
24
25 static
26 gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){
27   gfx_node_t *node = art_new(gfx_node_t,1);
28   if (node == NULL) return NULL;
29   node->type = type;
30   node->color = 0x0;        /* color of element  0xRRGGBBAA  alpha 0xff is solid*/
31   node->size =0.0;         /* font size, line width */
32   node->path = NULL;        /* path */
33   node->points = 0;
34   node->points_max =0;
35   node->svp = NULL;         /* svp */
36   node->filename = NULL;             /* font or image filename */
37   node->text = NULL;
38   node->x = 0.0;
39   node->y = 0.0;          /* position */
40   node->angle = 0;  
41   node->halign = GFX_H_NULL; /* text alignement */
42   node->valign = GFX_V_NULL; /* text alignement */
43   node->tabwidth = 0.0; 
44   node->next = NULL; 
45   if (canvas->lastnode != NULL){
46       canvas->lastnode->next = node;
47   }
48   if (canvas->firstnode == NULL){
49       canvas->firstnode = node;
50   }  
51   canvas->lastnode = node;
52   return node;
53 }
54
55 gfx_canvas_t *gfx_new_canvas (void) {
56     gfx_canvas_t *canvas = art_new(gfx_canvas_t,1);
57     canvas->firstnode = NULL;
58     canvas->lastnode = NULL;
59     return canvas;    
60 }
61
62 /* create a new line */
63 gfx_node_t  *gfx_new_line(gfx_canvas_t *canvas, 
64                            double x0, double y0, 
65                            double x1, double y1,
66                            double width, gfx_color_t color){
67
68   gfx_node_t *node;
69   ArtVpath *vec;
70   node = gfx_new_node(canvas,GFX_LINE);
71   if (node == NULL) return NULL;
72   vec = art_new(ArtVpath, 3);
73   if (vec == NULL) return NULL;
74   vec[0].code = ART_MOVETO_OPEN; vec[0].x=x0+LINEOFFSET; vec[0].y=y0+LINEOFFSET;
75   vec[1].code = ART_LINETO; vec[1].x=x1+LINEOFFSET; vec[1].y=y1+LINEOFFSET;
76   vec[2].code = ART_END;
77   
78   node->points = 3;
79   node->points_max = 3;
80   node->color = color;
81   node->size  = width;
82   node->path  = vec;
83   return node;
84 }
85
86 /* create a new area */
87 gfx_node_t   *gfx_new_area   (gfx_canvas_t *canvas, 
88                               double x0, double y0,
89                               double x1, double y1,
90                               double x2, double y2,
91                               gfx_color_t color) {
92
93   gfx_node_t *node;
94   ArtVpath *vec;
95   node = gfx_new_node(canvas,GFX_AREA);
96   if (node == NULL) return NULL;
97   vec = art_new(ArtVpath, 5);
98   if (vec == NULL) return NULL;
99   vec[0].code = ART_MOVETO; vec[0].x=x0; vec[0].y=y0;
100   vec[1].code = ART_LINETO; vec[1].x=x1; vec[1].y=y1;
101   vec[2].code = ART_LINETO; vec[2].x=x2; vec[2].y=y2;
102   vec[3].code = ART_LINETO; vec[3].x=x0; vec[3].y=y0;
103   vec[4].code = ART_END;
104   
105   node->points = 5;
106   node->points_max = 5;
107   node->color = color;
108   node->path  = vec;
109
110   return node;
111 }
112
113 /* add a point to a line or to an area */
114 int           gfx_add_point  (gfx_node_t *node, 
115                               double x, double y){
116   if (node == NULL) return 1;
117   if (node->type == GFX_AREA) {
118     double x0 = node->path[0].x;
119     double y0 = node->path[0].y;
120     node->points -= 2;
121     art_vpath_add_point (&(node->path),
122                          &(node->points),
123                          &(node->points_max),
124                          ART_LINETO,
125                          x,y);
126     art_vpath_add_point (&(node->path),
127                          &(node->points),
128                          &(node->points_max),
129                          ART_LINETO,
130                          x0,y0);
131     art_vpath_add_point (&(node->path),
132                          &(node->points),
133                          &(node->points_max),
134                          ART_END,
135                          0,0);
136   } else if (node->type == GFX_LINE) {
137     node->points -= 1;
138     art_vpath_add_point (&(node->path),
139                          &(node->points),
140                          &(node->points_max),
141                          ART_LINETO,
142                          x+LINEOFFSET,y+LINEOFFSET);
143     art_vpath_add_point (&(node->path),
144                          &(node->points),
145                          &(node->points_max),
146                          ART_END,
147                          0,0);
148     
149   } else {
150     /* can only add point to areas and lines */
151     return 1;
152   }
153   return 0;
154 }
155
156
157
158 /* create a text node */
159 gfx_node_t   *gfx_new_text   (gfx_canvas_t *canvas,  
160                               double x, double y, gfx_color_t color,
161                               char* font, double size,                        
162                               double tabwidth, double angle,
163                               enum gfx_h_align_en h_align,
164                               enum gfx_v_align_en v_align,
165                               char* text){
166    gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
167    if (angle != 0.0){
168        /* currently we only support 0 and 270 */
169        angle = 270.0;
170    }
171    
172    node->text = strdup(text);
173    node->size = size;
174    node->filename = strdup(font);
175    node->x = x;
176    node->y = y;
177    node->angle = angle;   
178    node->color = color;
179    node->tabwidth = tabwidth;
180    node->halign = h_align;
181    node->valign = v_align;
182    return node;
183 }
184
185 double gfx_get_text_width ( double start, char* font, double size,                            
186                             double tabwidth, char* text){
187
188   FT_GlyphSlot  slot;
189   FT_UInt       previous=0;
190   FT_UInt       glyph_index=0;
191   FT_Bool       use_kerning;
192   int           error;
193   FT_Face       face;
194   FT_Library    library=NULL;  
195   double        text_width=0;
196   FT_Init_FreeType( &library );
197   error = FT_New_Face( library, font, 0, &face );
198   if ( error ) return -1;
199   error = FT_Set_Char_Size(face,  size*64,size*64,  100,100);
200   if ( error ) return -1;
201
202   use_kerning = FT_HAS_KERNING(face);
203   slot = face->glyph;
204   for(;*text;text++) {  
205     previous = glyph_index;
206     glyph_index = FT_Get_Char_Index( face, *text);
207     
208     if (use_kerning && previous && glyph_index){
209       FT_Vector  delta;
210       FT_Get_Kerning( face, previous, glyph_index,
211                       0, &delta );
212       text_width += (double)delta.x / 64.0;
213       
214     }
215     error = FT_Load_Glyph( face, glyph_index, 0 );
216     if ( error ) {
217       FT_Done_FreeType(library);
218       return -1;
219     }
220     if (! previous) {
221       text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */        
222     }
223     text_width += (double)slot->metrics.horiAdvance / 64.0;
224   }
225   text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
226   text_width += (double)slot->metrics.width / 64.0; /* add just char width */
227   text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
228   FT_Done_FreeType(library);
229   return text_width;
230 }
231  
232
233
234
235 static int gfx_save_png (art_u8 *buffer, FILE *fp,
236                      long width, long height, long bytes_per_pixel);
237 /* render grafics into png image */
238 int           gfx_render_png (gfx_canvas_t *canvas, 
239                               art_u32 width, art_u32 height, 
240                               double zoom, 
241                               gfx_color_t background, FILE *fp){
242     
243     
244     FT_Library    library;
245     gfx_node_t *node = canvas->firstnode;    
246     art_u8 red = background >> 24, green = (background >> 16) & 0xff;
247     art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
248     unsigned long pys_width = width * zoom;
249     unsigned long pys_height = height * zoom;
250     const int bytes_per_pixel = 3;
251     unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
252     art_u8 *buffer = art_new (art_u8, rowstride*pys_height);
253     art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
254     FT_Init_FreeType( &library );
255     while(node){
256         switch (node->type) {
257         case GFX_LINE:
258         case GFX_AREA: {   
259             ArtVpath *vec;
260             double dst[6];     
261             ArtSVP *svp;
262             art_affine_scale(dst,zoom,zoom);
263             vec = art_vpath_affine_transform(node->path,dst);
264             if(node->type == GFX_LINE){
265                 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
266                                              ART_PATH_STROKE_CAP_ROUND,
267                                              node->size*zoom,1,1);
268             } else {
269                 svp = art_svp_from_vpath ( vec );
270             }
271             art_free(vec);
272             art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
273                                node->color, buffer, rowstride, NULL);
274             art_free(svp);
275             break;
276         }
277         case GFX_TEXT: {
278             int  error;
279             float text_width=0.0, text_height = 0.0;
280             unsigned char *text;
281             art_u8 fcolor[3],falpha;
282             FT_Face       face;
283             FT_GlyphSlot  slot;
284             FT_UInt       previous=0;
285             FT_UInt       glyph_index=0;
286             FT_Bool       use_kerning;
287
288             float pen_x = 0.0 , pen_y = 0.0;
289             /* double x,y; */
290             long   ix,iy,iz;
291             
292             fcolor[0] = node->color >> 24;
293             fcolor[1] = (node->color >> 16) & 0xff;
294             fcolor[2] = (node->color >> 8) & 0xff;
295             falpha = node->color & 0xff;
296             error = FT_New_Face( library,
297                                  (char *)node->filename,
298                                  0,
299                                  &face );
300             if ( error ) break;
301             use_kerning = FT_HAS_KERNING(face);
302
303             error = FT_Set_Char_Size(face,   /* handle to face object            */
304                                      (long)(node->size*64),
305                                      (long)(node->size*64),
306                                      (long)(100*zoom),
307                                      (long)(100*zoom));
308             if ( error ) break;
309             pen_x = node->x * zoom;
310             pen_y = node->y * zoom;
311             slot = face->glyph;
312
313             for(text=(unsigned char *)node->text;*text;text++) {        
314                 previous = glyph_index;
315                 glyph_index = FT_Get_Char_Index( face, *text);
316                 
317                 if (use_kerning && previous && glyph_index){
318                     FT_Vector  delta;
319                     FT_Get_Kerning( face, previous, glyph_index,
320                                     0, &delta );
321                     text_width += (double)delta.x / 64.0;
322                     
323                 }
324                 error = FT_Load_Glyph( face, glyph_index, 0 );
325                 if ( error ) break;
326                 if (previous == 0){
327                   pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */   
328                   text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */    
329                 }
330                 if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
331                   text_height = (double)slot->metrics.horiBearingY / 64.0;
332                 }
333                 text_width += (double)slot->metrics.horiAdvance / 64.0;
334             }
335             text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
336             text_width += (double)slot->metrics.width / 64.0; /* add just char width */
337             text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
338             
339             switch(node->halign){
340             case GFX_H_RIGHT:  pen_x -= text_width; break;
341             case GFX_H_CENTER: pen_x -= text_width / 2.0; break;          
342             case GFX_H_LEFT: break;          
343             }
344
345             switch(node->valign){
346             case GFX_V_TOP:    pen_y += text_height; break;
347             case GFX_V_CENTER: pen_y += text_height / 2.0; break;          
348             case GFX_V_BOTTOM: break;          
349             }
350
351             glyph_index=0;
352             for(text=(unsigned char *)node->text;*text;text++) {
353                 int gr;          
354                 previous = glyph_index;
355                 glyph_index = FT_Get_Char_Index( face, *text);
356                 
357                 if (use_kerning && previous && glyph_index){
358                     FT_Vector  delta;
359                     FT_Get_Kerning( face, previous, glyph_index,
360                                     0, &delta );
361                     pen_x += (double)delta.x / 64.0;
362                     
363                 }
364                 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
365                 if ( error ) break;
366                 gr = slot->bitmap.num_grays -1;
367                 for (iy=0; iy < slot->bitmap.rows; iy++){
368                     long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
369                     if (buf_y < 0 || buf_y >= pys_height) continue;
370                     buf_y *= rowstride;
371                     for (ix=0;ix < slot->bitmap.width;ix++){
372                         long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
373                         art_u8 font_alpha;
374                         
375                         if (buf_x < 0 || buf_x >= pys_width) continue;
376                         buf_x *=  bytes_per_pixel ;
377                         font_alpha =  *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
378                         font_alpha =  (art_u8)((double)font_alpha / gr * falpha);
379                         for (iz = 0; iz < 3; iz++){
380                             art_u8 *orig = buffer + buf_y + buf_x + iz;
381                             *orig =  (art_u8)((double)*orig / gr * ( gr - font_alpha) +
382                                               (double)fcolor[iz] / gr * (font_alpha));
383                         }
384                     }
385                 }
386                 pen_x += (double)slot->metrics.horiAdvance / 64.0;
387             }
388         }
389         }
390         node = node->next;
391     }  
392     gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
393     art_free(buffer);
394     FT_Done_FreeType( library );
395     return 0;    
396 }
397
398 /* free memory used by nodes this will also remove memory required for
399    associated paths and svcs ... but not for text strings */
400 int
401 gfx_destroy    (gfx_canvas_t *canvas){  
402   gfx_node_t *next,*node = canvas->firstnode;
403   while(node){
404     next = node->next;
405     art_free(node->path);
406     art_free(node->svp);
407     free(node->text);
408     free(node->filename);
409     art_free(node);
410     node = next;
411   }
412   return 0;
413 }
414  
415 static int gfx_save_png (art_u8 *buffer, FILE *fp,  long width, long height, long bytes_per_pixel){
416   png_structp png_ptr = NULL;
417   png_infop   info_ptr = NULL;
418   int i;
419   png_bytep *row_pointers;
420   int rowstride = width * bytes_per_pixel;
421   png_text text[2];
422   
423   if (fp == NULL)
424     return (1);
425
426   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
427   if (png_ptr == NULL)
428    {
429       return (1);
430    }
431    row_pointers = (png_bytepp)png_malloc(png_ptr,
432                                      height*sizeof(png_bytep));
433
434   info_ptr = png_create_info_struct(png_ptr);
435
436   if (info_ptr == NULL)
437     {
438       png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
439       return (1);
440     }
441
442   if (setjmp(png_jmpbuf(png_ptr)))
443     {
444       /* If we get here, we had a problem writing the file */
445       png_destroy_write_struct(&png_ptr, &info_ptr);
446       return (1);
447     }
448
449   png_init_io(png_ptr, fp);
450   png_set_IHDR (png_ptr, info_ptr,width, height,
451                 8, PNG_COLOR_TYPE_RGB,
452                 PNG_INTERLACE_NONE,
453                 PNG_COMPRESSION_TYPE_DEFAULT,
454                 PNG_FILTER_TYPE_DEFAULT);
455
456   text[0].key = "Software";
457   text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
458   text[0].compression = PNG_TEXT_COMPRESSION_NONE;
459   png_set_text (png_ptr, info_ptr, text, 1);
460
461   /* Write header data */
462   png_write_info (png_ptr, info_ptr);
463
464   for (i = 0; i < height; i++)
465     row_pointers[i] = (png_bytep) (buffer + i*rowstride);
466
467   png_write_image(png_ptr, row_pointers);
468   png_write_end(png_ptr, info_ptr);
469   png_destroy_write_struct(&png_ptr, &info_ptr);
470   return 1;
471 }
472
473  
474 static int svg_indent = 0;
475 static int svg_single_line = 0;
476 static void svg_print_indent(FILE *fp)
477 {
478   int i;
479    for (i = svg_indent - svg_single_line; i > 0; i--) {
480      putc(' ', fp);
481      putc(' ', fp);
482    }
483 }
484  
485 static void svg_start_tag(FILE *fp, const char *name)
486  {
487    svg_print_indent(fp);
488    putc('<', fp);
489    fputs(name, fp);
490    svg_indent++;
491  }
492  
493  static void svg_close_tag_single_line(FILE *fp)
494  {
495    svg_single_line++;
496    putc('>', fp);
497  }
498  
499  static void svg_close_tag(FILE *fp)
500  {
501    putc('>', fp);
502    if (!svg_single_line)
503      putc('\n', fp);
504  }
505  
506  static void svg_end_tag(FILE *fp, const char *name)
507  {
508    /* name is NULL if closing empty-node tag */
509    svg_indent--;
510    if (svg_single_line)
511      svg_single_line--;
512    else if (name)
513      svg_print_indent(fp);
514    if (name != NULL) {
515      fputs("</", fp);
516      fputs(name, fp);
517    } else {
518      putc('/', fp);
519    }
520    svg_close_tag(fp);
521  }
522  
523  static void svg_close_tag_empty_node(FILE *fp)
524  {
525    svg_end_tag(fp, NULL);
526  }
527  
528  static void svg_write_text(FILE *fp, const char *p)
529  {
530    char ch;
531    const char *start, *last;
532    if (!p)
533      return;
534    /* trim leading spaces */
535    while (*p == ' ')
536      p++;
537    start = p;
538    /* trim trailing spaces */
539    last = p - 1;
540    while ((ch = *p) != 0) {
541      if (ch != ' ')
542        last = p;
543      p++;
544   }
545    /* encode trimmed text */
546    p = start;
547    while (p <= last) {
548      ch = *p++;
549      switch (ch) {
550        case '&': fputs("&amp;", fp); break;
551        case '<': fputs("&lt;", fp); break;
552        case '>': fputs("&gt;", fp); break;
553        case '"': fputs("&quot;", fp); break;
554        default: putc(ch, fp);
555      }
556    }
557  }
558  
559  static void svg_write_number(FILE *fp, double d)
560  {
561    /* omit decimals if integer to reduce filesize */
562    char buf[60], *p;
563    snprintf(buf, sizeof(buf), "%.2f", d);
564    p = buf; /* doesn't trust snprintf return value */
565    while (*p)
566      p++;
567    while (--p > buf) {
568      char ch = *p;
569      if (ch == '0') {
570        *p = '\0'; /* zap trailing zeros */
571        continue;
572      }
573      if (ch == '.')
574        *p = '\0'; /* zap trailing dot */
575      break;
576    }
577    fputs(buf, fp);
578  }
579  
580  static int svg_color_is_black(int c)
581  {
582    /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB like html */
583    c = (int)((c >> 8) & 0xFFFFFF);
584    return !c;
585  }
586  
587  static void svg_write_color(FILE *fp, int c)
588  {
589    /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB like html */
590    c = (int)((c >> 8) & 0xFFFFFF);
591    if ((c & 0x0F0F0F) == ((c >> 4) & 0x0F0F0F)) {
592      /* css2 short form, #rgb is #rrggbb, not #r0g0b0 */
593      fprintf(fp, "#%03X",
594            ( ((c >> 8) & 0xF00)
595            | ((c >> 4) & 0x0F0)
596            | ( c       & 0x00F)));
597    } else {
598      fprintf(fp, "#%06X", c);
599    }
600  }
601  
602  static int svg_is_int_step(double a, double b)
603  {
604    double diff = fabs(a - b);
605    return floor(diff) == diff;
606  }
607  
608  static int svg_path_straight_segment(FILE *fp,
609      double lastA, double currentA, double currentB,
610      gfx_node_t *node,
611      int segment_idx, int isx, char absChar, char relChar)
612  {
613    if (!svg_is_int_step(lastA, currentA)) {
614      putc(absChar, fp);
615      svg_write_number(fp, currentA);
616      return 0;
617    }
618    if (segment_idx < node->points - 1) {
619      ArtVpath *vec = node->path + segment_idx + 1;
620      if (vec->code == ART_LINETO) {
621        double nextA = (isx ? vec->x : vec->y) - LINEOFFSET;
622        double nextB = (isx ? vec->y : vec->x) - LINEOFFSET;
623        if (nextB == currentB
624            && ((currentA >= lastA) == (nextA >= currentA))
625            && svg_is_int_step(currentA, nextA)) {
626          return 1; /* skip to next as it is a straight line  */
627        }
628      }
629    }
630    putc(relChar, fp);
631    svg_write_number(fp, currentA - lastA);
632    return 0;
633  }
634  
635  static void svg_path(FILE *fp, gfx_node_t *node, int multi)
636  {
637    int i;
638    double lastX = 0, lastY = 0;
639    /* for straight lines <path..> tags take less space than
640       <line..> tags because of the efficient packing
641       in the 'd' attribute */
642    svg_start_tag(fp, "path");
643    if (!multi) {
644      fputs(" stroke-width=\"", fp);
645      svg_write_number(fp, node->size);
646      fputs("\" stroke=\"", fp);
647      svg_write_color(fp, node->color);
648      fputs("\" fill=\"none\"", fp);
649    }
650    fputs(" d=\"", fp);
651    /* specification of the 'd' attribute: */
652    /* http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation */
653    for (i = 0; i < node->points; i++) {
654      ArtVpath *vec = node->path + i;
655      double x = vec->x - LINEOFFSET;
656      double y = vec->y - LINEOFFSET;
657      switch (vec->code) {
658      case ART_MOVETO_OPEN: /* fall-through */
659      case ART_MOVETO:
660        putc('M', fp);
661        svg_write_number(fp, x);
662        putc(',', fp);
663        svg_write_number(fp, y);
664        break;
665      case ART_LINETO:
666        /* try optimize filesize by using minimal lineto commands */
667        /* without introducing rounding errors. */
668        if (x == lastX) {
669          if (svg_path_straight_segment(fp, lastY, y, x, node, i, 0, 'V', 'v'))
670            continue;
671        } else if (y == lastY) {
672          if (svg_path_straight_segment(fp, lastX, x, y, node, i, 1, 'H', 'h'))
673            continue;
674        } else {
675          putc('L', fp);
676          svg_write_number(fp, x);
677          putc(',', fp);
678          svg_write_number(fp, y);
679        }
680        break;
681      case ART_CURVETO: break; /* unsupported */
682      case ART_END: break; /* nop */
683      }
684      lastX = x;
685      lastY = y;
686    }
687    fputs("\"", fp);
688    svg_close_tag_empty_node(fp);
689  }
690  
691  static void svg_multi_path(FILE *fp, gfx_node_t **nodeR)
692  {
693    /* optimize for multiple paths with the same color, penwidth, etc. */
694    int num = 1;
695    gfx_node_t *node = *nodeR;
696    gfx_node_t *next = node->next;
697    while (next) {
698      if (next->type != node->type
699          || next->size != node->size
700          || next->color != node->color)
701        break;
702      next = next->next;
703      num++;
704    }
705    if (num == 1) {
706      svg_path(fp, node, 0);
707      return;
708    }
709    svg_start_tag(fp, "g");
710    fputs(" stroke-width=\"", fp);
711    svg_write_number(fp, node->size);
712    fputs("\" stroke=\"", fp);
713    svg_write_color(fp, node->color);
714    fputs("\" fill=\"none\"", fp);
715    svg_close_tag(fp);
716    while (num && node) {
717      svg_path(fp, node, 1);
718      if (!--num)
719        break;
720      node = node->next;
721      *nodeR = node;
722    }
723    svg_end_tag(fp, "g");
724  }
725  
726  static void svg_area(FILE *fp, gfx_node_t *node)
727  {
728    int i;
729    double startX = 0, startY = 0;
730    svg_start_tag(fp, "polygon");
731    fputs(" fill=\"", fp);
732    svg_write_color(fp, node->color);
733    fputs("\" points=\"", fp);
734    for (i = 0; i < node->points; i++) {
735      ArtVpath *vec = node->path + i;
736      double x = vec->x - LINEOFFSET;
737      double y = vec->y - LINEOFFSET;
738      switch (vec->code) {
739        case ART_MOVETO_OPEN: /* fall-through */
740        case ART_MOVETO:
741          svg_write_number(fp, x);
742          putc(',', fp);
743          svg_write_number(fp, y);
744          startX = x;
745          startY = y;
746          break;
747        case ART_LINETO:
748          if (i == node->points - 2
749                         && node->path[i + 1].code == ART_END
750              && fabs(x - startX) < 0.001 && fabs(y - startY) < 0.001) {
751            break; /* poly area always closed, no need for last point */
752          }
753          putc(' ', fp);
754          svg_write_number(fp, x);
755          putc(',', fp);
756          svg_write_number(fp, y);
757          break;
758        case ART_CURVETO: break; /* unsupported */
759        case ART_END: break; /* nop */
760      }
761    }
762    fputs("\"", fp);
763    svg_close_tag_empty_node(fp);
764  }
765  
766  static void svg_text(FILE *fp, gfx_node_t *node)
767  {
768    double x = node->x - LINEOFFSET;
769    double y = node->y - LINEOFFSET;
770    if (node->angle != 0) {
771      svg_start_tag(fp, "g");
772      fputs(" transform=\"translate(", fp);
773      svg_write_number(fp, x);
774      fputs(",", fp);
775      svg_write_number(fp, y);
776      fputs(") rotate(", fp);
777      svg_write_number(fp, node->angle);
778      fputs(")\"", fp);
779      x = y = 0;
780      svg_close_tag(fp);
781    }
782    switch (node->valign) {
783    case GFX_V_TOP:  y += node->size; break;
784    case GFX_V_CENTER: y += node->size / 2; break;
785    case GFX_V_BOTTOM: break;
786    case GFX_V_NULL: break;
787    }
788    svg_start_tag(fp, "text");
789    fputs(" x=\"", fp);
790    svg_write_number(fp, x);
791    fputs("\" y=\"", fp);
792    svg_write_number(fp, y);
793    fputs("\" font-size=\"", fp);
794    svg_write_number(fp, node->size);
795    fputs("\"", fp);
796    if (!svg_color_is_black(node->color)) {
797      fputs(" fill=\"", fp);
798      svg_write_color(fp, node->color);
799      fputs("\"", fp);
800    }
801    switch (node->halign) {
802    case GFX_H_RIGHT:  fputs(" text-anchor=\"end\"", fp); break;
803    case GFX_H_CENTER: fputs(" text-anchor=\"middle\"", fp); break;
804    case GFX_H_LEFT: break;
805    case GFX_H_NULL: break;
806    }
807    svg_close_tag_single_line(fp);
808    /* support for node->tabwidth missing */
809    svg_write_text(fp, node->text);
810    svg_end_tag(fp, "text");
811    if (node->angle != 0)
812      svg_end_tag(fp, "g");
813  }
814  
815  int       gfx_render_svg (gfx_canvas_t *canvas,
816                  art_u32 width, art_u32 height,
817                  double zoom,
818                  gfx_color_t background, FILE *fp){
819    gfx_node_t *node = canvas->firstnode;
820    fputs(
821  "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
822  "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"
823  "   \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
824  "<!--\n"
825  "   SVG file created by RRDtool,\n"
826  "   Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch\n"
827  "\n"
828  "   The width/height attributes in the outhermost svg node\n"
829  "   are just default sizes for the browser which is used\n"
830  "   if the svg file is openened directly without being\n"
831  "   embedded in an html file.\n"
832  "   The viewBox is the local coord system for rrdtool.\n"
833  "-->\n", fp);
834    svg_start_tag(fp, "svg");
835    fputs(" width=\"", fp);
836    svg_write_number(fp, width * zoom);
837    fputs("\" height=\"", fp);
838    svg_write_number(fp, height * zoom);
839    fputs("\" x=\"0\" y=\"0\" viewBox=\"", fp);
840    svg_write_number(fp, -LINEOFFSET);
841    fputs(" ", fp);
842    svg_write_number(fp, -LINEOFFSET);
843    fputs(" ", fp);
844    svg_write_number(fp, width - LINEOFFSET);
845    fputs(" ", fp);
846    svg_write_number(fp, height - LINEOFFSET);
847    fputs("\" preserveAspectRatio=\"xMidYMid\"", fp);
848    fputs(" font-family=\"Helvetica\"", fp); /* default font */
849    svg_close_tag(fp);
850    svg_start_tag(fp, "rect");
851    fprintf(fp, " x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", width, height);
852    fputs(" style=\"fill:", fp);
853    svg_write_color(fp, background);
854    fputs("\"", fp);
855    svg_close_tag_empty_node(fp);
856    while (node) {
857      switch (node->type) {
858      case GFX_LINE:
859        svg_multi_path(fp, &node);
860        break;
861      case GFX_AREA:
862        svg_area(fp, node);
863        break;
864      case GFX_TEXT:
865        svg_text(fp, node);
866      }
867      node = node->next;
868    }
869    svg_end_tag(fp, "svg");
870    return 0;
871  }