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