prepare for the release of rrdtool-1.2.19
[rrdtool.git] / src / rrd_gfx.c
1 /****************************************************************************
2  * RRDtool 1.2.19  Copyright by Tobi Oetiker, 1997-2007
3  ****************************************************************************
4  * rrd_gfx.c  graphics wrapper for rrdtool
5   **************************************************************************/
6
7 /* #define DEBUG */
8
9 /* stupid MSVC doesnt support variadic macros = no debug for now! */
10 #ifdef _MSC_VER
11 # define DPRINTF()
12 #else 
13 # ifdef DEBUG
14 #  define DPRINTF(...)  fprintf(stderr, __VA_ARGS__);
15 # else
16 #  define DPRINTF(...)
17 # endif /* DEBUG */
18 #endif /* _MSC_VER */
19 #include "rrd_tool.h"
20 #include <png.h>
21 #include <ft2build.h>
22 #include FT_FREETYPE_H
23 #include FT_GLYPH_H
24
25 #include "rrd_gfx.h"
26 #include "rrd_afm.h"
27 #include "unused.h"
28
29 /* lines are better drawn on the pixle than between pixles */
30 #define LINEOFFSET 0.5
31
32 #define USE_PDF_FAKE_ALPHA 1
33 #define USE_EPS_FAKE_ALPHA 1
34
35 typedef struct gfx_char_s *gfx_char;
36 struct gfx_char_s {
37   FT_UInt     index;    /* glyph index */
38   FT_Vector   pos;      /* location from baseline in 26.6 */
39   FT_Glyph    image;    /* glyph bitmap */
40 };
41
42 typedef struct gfx_string_s *gfx_string;
43 struct gfx_string_s {
44   unsigned int    width;
45   unsigned int    height;
46   int             count;  /* number of characters */
47   gfx_char        glyphs;
48   size_t          num_glyphs;
49   FT_BBox         bbox;
50   FT_Matrix       transform;
51 };
52
53 /* compute string bbox */
54 static void compute_string_bbox(gfx_string string);
55
56 /* create a freetype glyph string */
57 gfx_string gfx_string_create ( gfx_canvas_t *canvas, FT_Face face,
58                                const char *text, int rotation, double tabwidth, double size);
59
60 /* create a freetype glyph string */
61 static void gfx_string_destroy ( gfx_string string );
62
63 static
64 gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){
65   gfx_node_t *node = art_new(gfx_node_t,1);
66   if (node == NULL) return NULL;
67   node->type = type;
68   node->color = 0x0;        /* color of element  0xRRGGBBAA  alpha 0xff is solid*/
69   node->size =0.0;         /* font size, line width */
70   node->path = NULL;        /* path */
71   node->points = 0;
72   node->points_max =0;
73   node->closed_path = 0;
74   node->filename = NULL;             /* font or image filename */
75   node->text = NULL;
76   node->x = 0.0;
77   node->y = 0.0;          /* position */
78   node->angle = 0;  
79   node->halign = GFX_H_NULL; /* text alignement */
80   node->valign = GFX_V_NULL; /* text alignement */
81   node->tabwidth = 0.0; 
82   node->next = NULL; 
83   if (canvas->lastnode != NULL){
84       canvas->lastnode->next = node;
85   }
86   if (canvas->firstnode == NULL){
87       canvas->firstnode = node;
88   }  
89   canvas->lastnode = node;
90   return node;
91 }
92
93 gfx_canvas_t *gfx_new_canvas (void) {
94     gfx_canvas_t *canvas = art_new(gfx_canvas_t,1);
95     canvas->firstnode = NULL;
96     canvas->lastnode = NULL;
97     canvas->imgformat = IF_PNG; /* we default to PNG output */
98     canvas->interlaced = 0;
99     canvas->zoom = 1.0;
100     canvas->font_aa_threshold = -1.0;
101     canvas->aa_type = AA_NORMAL;
102     return canvas;
103 }
104
105 /* create a new line */
106 gfx_node_t  *gfx_new_line(gfx_canvas_t *canvas, 
107                            double X0, double Y0, 
108                            double X1, double Y1,
109                            double width, gfx_color_t color){
110   return gfx_new_dashed_line(canvas, X0, Y0, X1, Y1, width, color, 0, 0);
111 }
112
113 gfx_node_t  *gfx_new_dashed_line(gfx_canvas_t *canvas, 
114                            double X0, double Y0, 
115                            double X1, double Y1,
116                            double width, gfx_color_t color,
117                            double dash_on, double dash_off){
118
119   gfx_node_t *node;
120   ArtVpath *vec;
121   node = gfx_new_node(canvas,GFX_LINE);
122   if (node == NULL) return NULL;
123   vec = art_new(ArtVpath, 3);
124   if (vec == NULL) return NULL;
125   vec[0].code = ART_MOVETO_OPEN; vec[0].x=X0+LINEOFFSET; vec[0].y=Y0+LINEOFFSET;
126   vec[1].code = ART_LINETO; vec[1].x=X1+LINEOFFSET; vec[1].y=Y1+LINEOFFSET;
127   vec[2].code = ART_END; vec[2].x=0;vec[2].y=0;
128   
129   node->points = 3;
130   node->points_max = 3;
131   node->color = color;
132   node->size  = width;
133   node->dash_on = dash_on;
134   node->dash_off = dash_off;
135   node->path  = vec;
136   return node;
137 }
138
139 /* create a new area */
140 gfx_node_t   *gfx_new_area   (gfx_canvas_t *canvas, 
141                               double X0, double Y0,
142                               double X1, double Y1,
143                               double X2, double Y2,
144                               gfx_color_t color) {
145
146   gfx_node_t *node;
147   ArtVpath *vec;
148   node = gfx_new_node(canvas,GFX_AREA);
149   if (node == NULL) return NULL;
150   vec = art_new(ArtVpath, 5);
151   if (vec == NULL) return NULL;
152   vec[0].code = ART_MOVETO; vec[0].x=X0; vec[0].y=Y0;
153   vec[1].code = ART_LINETO; vec[1].x=X1; vec[1].y=Y1;
154   vec[2].code = ART_LINETO; vec[2].x=X2; vec[2].y=Y2;
155   vec[3].code = ART_LINETO; vec[3].x=X0; vec[3].y=Y0;
156   vec[4].code = ART_END; vec[4].x=0; vec[4].y=0;
157   
158   node->points = 5;
159   node->points_max = 5;
160   node->color = color;
161   node->path  = vec;
162
163   return node;
164 }
165
166 /* add a point to a line or to an area */
167 int           gfx_add_point  (gfx_node_t *node, 
168                               double x, double y){
169   if (node == NULL) return 1;
170   if (node->type == GFX_AREA) {
171     double X0 = node->path[0].x;
172     double Y0 = node->path[0].y;
173     node->points -= 2;
174     art_vpath_add_point (&(node->path),
175                          &(node->points),
176                          &(node->points_max),
177                          ART_LINETO,
178                          x,y);
179     art_vpath_add_point (&(node->path),
180                          &(node->points),
181                          &(node->points_max),
182                          ART_LINETO,
183                          X0,Y0);
184     art_vpath_add_point (&(node->path),
185                          &(node->points),
186                          &(node->points_max),
187                          ART_END,
188                          0,0);
189   } else if (node->type == GFX_LINE) {
190     node->points -= 1;
191     art_vpath_add_point (&(node->path),
192                          &(node->points),
193                          &(node->points_max),
194                          ART_LINETO,
195                          x+LINEOFFSET,y+LINEOFFSET);
196     art_vpath_add_point (&(node->path),
197                          &(node->points),
198                          &(node->points_max),
199                          ART_END,
200                          0,0);
201     
202   } else {
203     /* can only add point to areas and lines */
204     return 1;
205   }
206   return 0;
207 }
208
209 void           gfx_close_path  (gfx_node_t *node) {
210     node->closed_path = 1;
211     if (node->path[0].code == ART_MOVETO_OPEN)
212         node->path[0].code = ART_MOVETO;
213 }
214
215 /* create a text node */
216 gfx_node_t   *gfx_new_text   (gfx_canvas_t *canvas,  
217                               double x, double y, gfx_color_t color,
218                               char* font, double size,                        
219                               double tabwidth, double angle,
220                               enum gfx_h_align_en h_align,
221                               enum gfx_v_align_en v_align,
222                               char* text){
223    gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
224    
225    node->text = strdup(text);
226    node->size = size;
227    node->filename = strdup(font);
228    node->x = x;
229    node->y = y;
230    node->angle = angle;   
231    node->color = color;
232    node->tabwidth = tabwidth;
233    node->halign = h_align;
234    node->valign = v_align;
235 #if 0
236   /* debugging: show text anchor
237      green is along x-axis, red is downward y-axis */
238    if (1) {
239      double a = 2 * M_PI * -node->angle / 360.0;
240      double cos_a = cos(a);
241      double sin_a = sin(a);
242      double len = 3;
243      gfx_new_line(canvas,
244          x, y,
245          x + len * cos_a, y - len * sin_a,
246          0.2, 0x00FF0000);
247      gfx_new_line(canvas,
248          x, y,
249          x + len * sin_a, y + len * cos_a,
250          0.2, 0xFF000000);
251    }
252 #endif
253    return node;
254 }
255
256 int           gfx_render(gfx_canvas_t *canvas, 
257                               art_u32 width, art_u32 height, 
258                               gfx_color_t background, FILE *fp){
259   switch (canvas->imgformat) {
260   case IF_PNG: 
261     return gfx_render_png (canvas, width, height, background, fp);
262   case IF_SVG: 
263     return gfx_render_svg (canvas, width, height, background, fp);
264   case IF_EPS:
265     return gfx_render_eps (canvas, width, height, background, fp);
266   case IF_PDF:
267     return gfx_render_pdf (canvas, width, height, background, fp);
268   default:
269     return -1;
270   }
271 }
272
273 static void gfx_string_destroy ( gfx_string string ) {
274   unsigned int n;
275   if (string->glyphs) {
276     for (n=0; n<string->num_glyphs; ++n)
277       FT_Done_Glyph (string->glyphs[n].image);
278     free (string->glyphs);
279   }
280   free (string);
281 }
282
283
284 double gfx_get_text_width ( gfx_canvas_t *canvas,
285                             double start, char* font, double size,
286                             double tabwidth, char* text, int rotation){
287   switch (canvas->imgformat) {
288   case IF_PNG: 
289     return gfx_get_text_width_libart (canvas, start, font, size, tabwidth, text, rotation);
290   case IF_SVG: /* fall through */ 
291   case IF_EPS:
292   case IF_PDF:
293     return afm_get_text_width(start, font, size, tabwidth, text);
294   default:
295     return size * strlen(text);
296   }
297 }
298
299 double gfx_get_text_width_libart (
300                             gfx_canvas_t *canvas, double UNUSED(start), char* font, double size,
301                             double tabwidth, char* text, int rotation ){
302
303   int           error;
304   double        text_width=0;
305   FT_Face       face;
306   FT_Library    library=NULL;  
307   gfx_string    string;
308
309   FT_Init_FreeType( &library );
310   error = FT_New_Face( library, font, 0, &face );
311   if ( error ) {
312     FT_Done_FreeType(library);
313     return -1;
314   }
315   error = FT_Set_Char_Size(face,  size*64,size*64,  100,100);
316   if ( error ) {
317     FT_Done_FreeType(library);
318     return -1;
319   }
320   string = gfx_string_create( canvas, face, text, rotation, tabwidth, size );
321   text_width = string->width;
322   gfx_string_destroy(string);
323   FT_Done_FreeType(library);
324   return text_width/64;
325 }
326
327 static void gfx_libart_close_path(gfx_node_t *node, ArtVpath **vec)
328 {
329     /* libart must have end==start for closed paths,
330        even if using ART_MOVETO and not ART_MOVETO_OPEN
331        so add extra point which is the same as the starting point */
332     int points_max = node->points; /* scaled array has exact size */
333     int points = node->points - 1;
334     art_vpath_add_point (vec, &points, &points_max, ART_LINETO,
335             (**vec).x, (**vec).y);
336     art_vpath_add_point (vec, &points, &points_max, ART_END, 0, 0);
337 }
338
339
340 /* find bbox of a string */
341 static void compute_string_bbox(gfx_string string) {
342     unsigned int n;
343     FT_BBox bbox;
344
345     bbox.xMin = bbox.yMin = 32000;
346     bbox.xMax = bbox.yMax = -32000;
347     for ( n = 0; n < string->num_glyphs; n++ ) {
348       FT_BBox glyph_bbox;
349       FT_Glyph_Get_CBox( string->glyphs[n].image, ft_glyph_bbox_gridfit,
350        &glyph_bbox );
351       if (glyph_bbox.xMin < bbox.xMin) {
352          bbox.xMin = glyph_bbox.xMin;
353       }
354       if (glyph_bbox.yMin < bbox.yMin) {
355         bbox.yMin = glyph_bbox.yMin;
356       }
357       if (glyph_bbox.xMax > bbox.xMax) {
358          bbox.xMax = glyph_bbox.xMax;
359       }
360       if (glyph_bbox.yMax > bbox.yMax) {
361          bbox.yMax = glyph_bbox.yMax;
362       }
363     }
364     if ( bbox.xMin > bbox.xMax ) { 
365       bbox.xMin = 0;
366       bbox.yMin = 0;
367       bbox.xMax = 0;
368       bbox.yMax = 0;
369     }
370     string->bbox.xMin = bbox.xMin;
371     string->bbox.xMax = bbox.xMax;
372     string->bbox.yMin = bbox.yMin;
373     string->bbox.yMax = bbox.yMax;
374
375
376 /* create a free type glyph string */
377 gfx_string gfx_string_create(gfx_canvas_t *canvas, FT_Face face,const char *text,
378         int rotation, double tabwidth, double size )
379 {
380
381   FT_GlyphSlot  slot = face->glyph;  /* a small shortcut */
382   FT_Bool       use_kerning;
383   FT_UInt       previous;
384   FT_Vector     ft_pen;
385
386   gfx_string    string = (gfx_string) malloc (sizeof(struct gfx_string_s));
387
388   gfx_char      glyph;          /* current glyph in table */
389   int           n;
390   int           error;
391   int        gottab = 0;    
392
393 #ifdef HAVE_MBSTOWCS
394   wchar_t       *cstr;
395   size_t        clen = strlen(text)+1;
396   cstr = malloc(sizeof(wchar_t) * clen); /* yes we are allocating probably too much here, I know */
397   string->count=mbstowcs(cstr,text,clen);
398   if ( string->count == -1){
399   /* conversion did not work, so lets fall back to just use what we got */
400         string->count=clen-1;
401         for(n=0;text[n] != '\0';n++){
402             cstr[n]=(unsigned char)text[n];
403         }
404   }
405 #else
406   char          *cstr = strdup(text);
407   string->count = strlen (text);
408 #endif
409
410   ft_pen.x = 0;   /* start at (0,0) !! */
411   ft_pen.y = 0;
412
413
414   string->width = 0;
415   string->height = 0;
416   string->glyphs = (gfx_char) calloc (string->count,sizeof(struct gfx_char_s));
417   string->num_glyphs = 0;
418   string->transform.xx = (FT_Fixed)( cos(M_PI*(rotation)/180.0)*0x10000);
419   string->transform.xy = (FT_Fixed)(-sin(M_PI*(rotation)/180.0)*0x10000);
420   string->transform.yx = (FT_Fixed)( sin(M_PI*(rotation)/180.0)*0x10000);
421   string->transform.yy = (FT_Fixed)( cos(M_PI*(rotation)/180.0)*0x10000);
422
423   use_kerning = FT_HAS_KERNING(face);
424   previous    = 0;
425   glyph = string->glyphs;
426   for (n=0; n<string->count;glyph++,n++) {
427     FT_Vector   vec;
428     /* handle the tabs ...
429        have a witespace glyph inserted, but set its width such that the distance
430     of the new right edge is x times tabwidth from 0,0 where x is an integer. */    
431     unsigned int letter = cstr[n];
432         letter = afm_fix_osx_charset(letter); /* unsafe macro */
433           
434     gottab = 0;
435     if (letter == '\\' && n+1 < string->count && cstr[n+1] == 't'){
436             /* we have a tab here so skip the backslash and
437                set t to ' ' so that we get a white space */
438             gottab = 1;
439             n++;
440             letter  = ' ';            
441     }            
442     if (letter == '\t'){
443         letter = ' ';
444         gottab = 1 ;
445     }            
446     /* initialize each struct gfx_char_s */
447     glyph->index = 0;
448     glyph->pos.x = 0;
449     glyph->pos.y = 0;
450     glyph->image = NULL;
451     glyph->index = FT_Get_Char_Index( face, letter );
452
453     /* compute glyph origin */
454     if ( use_kerning && previous && glyph->index ) {
455       FT_Vector kerning;
456       FT_Get_Kerning (face, previous, glyph->index,
457           ft_kerning_default, &kerning);
458       ft_pen.x += kerning.x;
459       ft_pen.y += kerning.y;
460     }
461
462     /* load the glyph image (in its native format) */
463     /* for now, we take a monochrome glyph bitmap */
464     error = FT_Load_Glyph (face, glyph->index, size > canvas->font_aa_threshold ?
465                             canvas->aa_type == AA_NORMAL ? FT_LOAD_TARGET_NORMAL :
466                             canvas->aa_type == AA_LIGHT ? FT_LOAD_TARGET_LIGHT :
467                             FT_LOAD_TARGET_MONO : FT_LOAD_TARGET_MONO);
468     if (error) {
469       DPRINTF("couldn't load glyph:  %c\n", letter)
470       continue;
471     }
472     error = FT_Get_Glyph (slot, &glyph->image);
473     if (error) {
474       DPRINTF("couldn't get glyph %c from slot %d\n", letter, (int)slot)
475       continue;
476     }
477     /* if we are in tabbing mode, we replace the tab with a space and shift the position
478        of the space so that its left edge is where the tab was supposed to land us */
479     if (gottab){
480        /* we are in gridfitting mode so the calculations happen in 1/64 pixles */
481         ft_pen.x = tabwidth*64.0 * (float)(1 + (long)(ft_pen.x / (tabwidth * 64.0))) - slot->advance.x;
482     }
483     /* store current pen position */
484     glyph->pos.x = ft_pen.x;
485     glyph->pos.y = ft_pen.y;
486
487
488     ft_pen.x   += slot->advance.x;    
489     ft_pen.y   += slot->advance.y;
490
491     /* rotate glyph */
492     vec = glyph->pos;
493     FT_Vector_Transform (&vec, &string->transform);
494     error = FT_Glyph_Transform (glyph->image, &string->transform, &vec);
495     if (error) {
496       DPRINTF("couldn't transform glyph id %d\n", letter)
497       continue;
498     }
499
500     /* convert to a bitmap - destroy native image */
501     error = FT_Glyph_To_Bitmap (&glyph->image, size > canvas->font_aa_threshold ?
502                             canvas->aa_type == AA_NORMAL ? FT_RENDER_MODE_NORMAL :
503                             canvas->aa_type == AA_LIGHT ? FT_RENDER_MODE_LIGHT :
504                             FT_RENDER_MODE_MONO : FT_RENDER_MODE_MONO, 0, 1);
505     if (error) {
506       DPRINTF("couldn't convert glyph id %d to bitmap\n", letter)
507       continue;
508     }
509
510     /* increment number of glyphs */
511     previous = glyph->index;
512     string->num_glyphs++;
513   }
514   free(cstr);
515 /*  printf ("number of glyphs = %d\n", string->num_glyphs);*/
516   compute_string_bbox( string );
517   /* the last character was a tab */  
518   /* if (gottab) { */
519       string->width = ft_pen.x;
520   /* } else {
521       string->width = string->bbox.xMax - string->bbox.xMin;
522   } */
523   string->height = string->bbox.yMax - string->bbox.yMin;
524   return string;
525 }
526
527
528 static int gfx_save_png (art_u8 *buffer, FILE *fp,
529                      long width, long height, long bytes_per_pixel);
530 /* render grafics into png image */
531
532 int           gfx_render_png (gfx_canvas_t *canvas, 
533                               art_u32 width, art_u32 height, 
534                               gfx_color_t background, FILE *fp){
535     
536     
537     FT_Library    library;
538     gfx_node_t *node = canvas->firstnode;    
539     /*
540     art_u8 red = background >> 24, green = (background >> 16) & 0xff;
541     art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
542     */
543     unsigned long pys_width = width * canvas->zoom;
544     unsigned long pys_height = height * canvas->zoom;
545     const int bytes_per_pixel = 4;
546     unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
547     
548     /* fill that buffer with out background color */
549     gfx_color_t *buffp = art_new (gfx_color_t, pys_width*pys_height);
550     art_u8 *buffer = (art_u8 *)buffp;
551     unsigned long i;
552     for (i=0;i<pys_width*pys_height;
553          i++){
554         *(buffp++)=background;
555     }
556     FT_Init_FreeType( &library );
557     while(node){
558         switch (node->type) {
559         case GFX_LINE:
560         case GFX_AREA: {   
561             ArtVpath *vec;
562             double dst[6];     
563             ArtSVP *svp;
564             art_affine_scale(dst,canvas->zoom,canvas->zoom);
565             vec = art_vpath_affine_transform(node->path,dst);
566             if (node->closed_path)
567                 gfx_libart_close_path(node, &vec);
568             /* gfx_round_scaled_coordinates(vec); */
569             /* pvec = art_vpath_perturb(vec);
570                art_free(vec); */
571             if(node->type == GFX_LINE){
572                 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
573                                              ART_PATH_STROKE_CAP_ROUND,
574                                              node->size*canvas->zoom,4,0.25);
575             } else {
576                 svp  = art_svp_from_vpath ( vec );
577                 /* this takes time and is unnecessary since we make
578                    sure elsewhere that the areas are going clock-whise */
579                 /*  svpt = art_svp_uncross( svp );
580                     art_svp_free(svp);
581                     svp  = art_svp_rewind_uncrossed(svpt,ART_WIND_RULE_NONZERO); 
582                     art_svp_free(svpt);
583                  */
584             }
585             art_free(vec);
586             /* this is from gnome since libart does not have this yet */
587             gnome_print_art_rgba_svp_alpha (svp ,0,0, pys_width, pys_height,
588                                 node->color, buffer, rowstride, NULL);
589             art_svp_free(svp);
590             break;
591         }
592         case GFX_TEXT: {
593             unsigned int  n;
594             int  error;
595             art_u8 fcolor[4],falpha;
596             FT_Face       face;
597             gfx_char      glyph;
598             gfx_string    string;
599             FT_Vector     vec;  /* 26.6 */
600
601             float pen_x = 0.0 , pen_y = 0.0;
602             /* double x,y; */
603             long   ix,iy;
604             
605             fcolor[0] = node->color >> 24;
606             fcolor[1] = (node->color >> 16) & 0xff;
607             fcolor[2] = (node->color >> 8) & 0xff;
608             falpha = node->color & 0xff;
609             error = FT_New_Face( library,
610                                  (char *)node->filename,
611                                  0,
612                                  &face );
613             if ( error ) {
614                 rrd_set_error("failed to load %s",node->filename);
615                 
616                 break;
617             }
618             error = FT_Set_Char_Size(face,   /* handle to face object            */
619                                      (long)(node->size*64),
620                                      (long)(node->size*64),
621                                      (long)(100*canvas->zoom),
622                                      (long)(100*canvas->zoom));
623             if ( error ) break;
624             pen_x = node->x * canvas->zoom;
625             pen_y = node->y * canvas->zoom;
626
627             string = gfx_string_create (canvas, face, node->text, node->angle, node->tabwidth, node->size);
628             switch(node->halign){
629             case GFX_H_RIGHT:  vec.x = -string->bbox.xMax;
630                                break;          
631             case GFX_H_CENTER: vec.x = abs(string->bbox.xMax) >= abs(string->bbox.xMin) ?
632                                        -string->bbox.xMax/2:-string->bbox.xMin/2;
633                                break;          
634             case GFX_H_LEFT:   vec.x = -string->bbox.xMin;
635                                break;
636             case GFX_H_NULL:   vec.x = 0;
637                                break;          
638             }
639
640             switch(node->valign){
641             case GFX_V_TOP:    vec.y = string->bbox.yMax;
642                                break;
643             case GFX_V_CENTER: vec.y = abs(string->bbox.yMax) >= abs(string->bbox.yMin) ?
644                                        string->bbox.yMax/2:string->bbox.yMin/2;
645                                break;
646             case GFX_V_BOTTOM: vec.y = 0;
647                                break;
648             case GFX_V_NULL:   vec.y = 0;
649                                break;
650             }
651             pen_x += vec.x/64;
652             pen_y += vec.y/64;
653             glyph = string->glyphs;
654             for(n=0; n<string->num_glyphs; n++, glyph++) {
655                 int gr;
656                 FT_Glyph        image;
657                 FT_BitmapGlyph  bit;
658                 /* long buf_x,comp_n; */
659                 /* make copy to transform */
660                 if (! glyph->image) {
661                   DPRINTF("no image\n")
662                   continue;
663                 }
664                 error = FT_Glyph_Copy (glyph->image, &image);
665                 if (error) {
666                   DPRINTF("couldn't copy image\n")
667                   continue;
668                 }
669
670                 /* transform it */
671                 vec = glyph->pos;
672                 FT_Vector_Transform (&vec, &string->transform);
673
674                 bit = (FT_BitmapGlyph) image;
675                 gr = bit->bitmap.num_grays -1;
676 /* 
677                 buf_x = (pen_x + 0.5) + (double)bit->left;
678                 comp_n = buf_x + bit->bitmap.width > pys_width ? pys_width - buf_x : bit->bitmap.width;
679                 if (buf_x < 0 || buf_x >= (long)pys_width) continue;
680                 buf_x *=  bytes_per_pixel ;
681                 for (iy=0; iy < bit->bitmap.rows; iy++){                    
682                     long buf_y = iy+(pen_y+0.5)-(double)bit->top;
683                     if (buf_y < 0 || buf_y >= (long)pys_height) continue;
684                     buf_y *= rowstride;
685                     for (ix=0;ix < bit->bitmap.width;ix++){             
686                         *(letter + (ix*bytes_per_pixel+3)) = *(bit->bitmap.buffer + iy * bit->bitmap.width + ix);
687                     }
688                     art_rgba_rgba_composite(buffer + buf_y + buf_x ,letter,comp_n);
689                  }
690                  art_free(letter);
691 */
692                 switch ( bit->bitmap.pixel_mode ) {
693                     case FT_PIXEL_MODE_GRAY:
694                         for (iy=0; iy < bit->bitmap.rows; iy++){
695                             long buf_y = iy+(pen_y+0.5)-bit->top;
696                             if (buf_y < 0 || buf_y >= (long)pys_height) continue;
697                             buf_y *= rowstride;
698                             for (ix=0;ix < bit->bitmap.width;ix++){
699                                 long buf_x = ix + (pen_x + 0.5) + (double)bit->left ;
700                                 art_u8 font_alpha;
701
702                                 if (buf_x < 0 || buf_x >= (long)pys_width) continue;
703                                 buf_x *=  bytes_per_pixel ;
704                                 font_alpha =  *(bit->bitmap.buffer + iy * bit->bitmap.pitch + ix);
705                     if (font_alpha > 0){
706                                     fcolor[3] =  (art_u8)((double)font_alpha / gr * falpha);
707                         art_rgba_rgba_composite(buffer + buf_y + buf_x ,fcolor,1);
708                                 }
709                             }
710                         }
711                         break;
712
713                     case FT_PIXEL_MODE_MONO:
714                         for (iy=0; iy < bit->bitmap.rows; iy++){
715                             long buf_y = iy+(pen_y+0.5)-bit->top;
716                             if (buf_y < 0 || buf_y >= (long)pys_height) continue;
717                             buf_y *= rowstride;
718                             for (ix=0;ix < bit->bitmap.width;ix++){
719                                 long buf_x = ix + (pen_x + 0.5) + (double)bit->left ;
720
721                                 if (buf_x < 0 || buf_x >= (long)pys_width) continue;
722                                 buf_x *=  bytes_per_pixel ;
723                                 if ( (fcolor[3] = falpha * ((*(bit->bitmap.buffer + iy * bit->bitmap.pitch + ix/8) >> (7 - (ix % 8))) & 1)) > 0 )
724                                     art_rgba_rgba_composite(buffer + buf_y + buf_x ,fcolor,1);
725                             }
726                         }
727                         break;
728
729                         default:
730                             rrd_set_error("unknown freetype pixel mode: %d", bit->bitmap.pixel_mode);
731                             break;
732                 }
733
734 /*
735                 for (iy=0; iy < bit->bitmap.rows; iy++){                    
736                     long buf_y = iy+(pen_y+0.5)-bit->top;
737                     if (buf_y < 0 || buf_y >= (long)pys_height) continue;
738                     buf_y *= rowstride;
739                     for (ix=0;ix < bit->bitmap.width;ix++){
740                         long buf_x = ix + (pen_x + 0.5) + (double)bit->left ;
741                         art_u8 font_alpha;
742                         
743                         if (buf_x < 0 || buf_x >= (long)pys_width) continue;
744                         buf_x *=  bytes_per_pixel ;
745                         font_alpha =  *(bit->bitmap.buffer + iy * bit->bitmap.width + ix);
746                         font_alpha =  (art_u8)((double)font_alpha / gr * falpha);
747                         for (iz = 0; iz < 3; iz++){
748                             art_u8 *orig = buffer + buf_y + buf_x + iz;
749                             *orig =  (art_u8)((double)*orig / gr * ( gr - font_alpha) +
750                                               (double)fcolor[iz] / gr * (font_alpha));
751                         }
752                     }
753                 }
754 */
755                 FT_Done_Glyph (image);
756             }
757             gfx_string_destroy(string);
758         }
759         }
760         node = node->next;
761     }  
762     gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
763     art_free(buffer);
764     FT_Done_FreeType( library );
765     return 0;    
766 }
767
768 /* free memory used by nodes this will also remove memory required for
769    associated paths and svcs ... but not for text strings */
770 int
771 gfx_destroy    (gfx_canvas_t *canvas){  
772   gfx_node_t *next,*node = canvas->firstnode;
773   while(node){
774     next = node->next;
775     art_free(node->path);
776     free(node->text);
777     free(node->filename);
778     art_free(node);
779     node = next;
780   }
781   art_free(canvas);
782   return 0;
783 }
784  
785 static int gfx_save_png (art_u8 *buffer, FILE *fp,  long width, long height, long bytes_per_pixel){
786   png_structp png_ptr = NULL;
787   png_infop   info_ptr = NULL;
788   int i;
789   png_bytep *row_pointers;
790   int rowstride = width * bytes_per_pixel;
791   png_text text[2];
792   
793   if (fp == NULL)
794     return (1);
795
796   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
797   if (png_ptr == NULL)
798    {
799       return (1);
800    }
801    row_pointers = (png_bytepp)png_malloc(png_ptr,
802                                      height*sizeof(png_bytep));
803
804   info_ptr = png_create_info_struct(png_ptr);
805
806   if (info_ptr == NULL)
807     {
808       png_free(png_ptr,row_pointers);
809       png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
810       return (1);
811     }
812
813   if (setjmp(png_jmpbuf(png_ptr)))
814     {
815       /* If we get here, we had a problem writing the file */
816       png_destroy_write_struct(&png_ptr, &info_ptr);
817       return (1);
818     }
819
820   png_init_io(png_ptr, fp);
821   png_set_IHDR (png_ptr, info_ptr,width, height,
822                 8, PNG_COLOR_TYPE_RGB_ALPHA,
823                 PNG_INTERLACE_NONE,
824                 PNG_COMPRESSION_TYPE_DEFAULT,
825                 PNG_FILTER_TYPE_DEFAULT);
826
827   text[0].key = "Software";
828   text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
829   text[0].compression = PNG_TEXT_COMPRESSION_NONE;
830   png_set_text (png_ptr, info_ptr, text, 1);
831
832   /* lets make this fast while ending up with some increass in image size */
833   png_set_filter(png_ptr,0,PNG_FILTER_NONE);
834   /* png_set_filter(png_ptr,0,PNG_FILTER_SUB); */
835   png_set_compression_level(png_ptr,1);
836   /* png_set_compression_strategy(png_ptr,Z_HUFFMAN_ONLY); */
837   /* 
838   png_set_filter(png_ptr,PNG_FILTER_TYPE_BASE,PNG_FILTER_SUB);
839   png_set_compression_strategy(png_ptr,Z_HUFFMAN_ONLY);
840   png_set_compression_level(png_ptr,Z_BEST_SPEED); */
841   
842   /* Write header data */
843   png_write_info (png_ptr, info_ptr);
844   for (i = 0; i < height; i++)
845     row_pointers[i] = (png_bytep) (buffer + i*rowstride);
846   
847   png_write_image(png_ptr, row_pointers);
848   png_write_end(png_ptr, info_ptr);
849   png_free(png_ptr,row_pointers);
850   png_destroy_write_struct(&png_ptr, &info_ptr);
851   return 1;
852 }
853
854  
855 /* ----- COMMON ROUTINES for pdf, svg and eps */
856 #define min3(a, b, c) (a < b ? (a < c ? a : c) : (b < c ? b : c))
857 #define max3(a, b, c) (a > b ? (a > c ? a : c) : (b > c ? b : c))
858
859 #define PDF_CALC_DEBUG 0
860
861 typedef struct pdf_point
862 {
863         double x, y;
864 } pdf_point;
865
866 typedef struct
867 {
868         double ascender, descender, baselineY;
869         pdf_point sizep, minp, maxp;
870         double x, y, tdx, tdy;
871         double r, cos_r, sin_r;
872         double ma, mb, mc, md, mx, my; /* pdf coord matrix */
873         double tmx, tmy; /* last 2 coords of text coord matrix */
874 #if PDF_CALC_DEBUG
875         int debug;
876 #endif
877 } pdf_coords;
878
879 #if PDF_CALC_DEBUG
880 static void pdf_dump_calc(gfx_node_t *node, pdf_coords *g)
881 {
882         fprintf(stderr, "PDF CALC =============================\n");
883         fprintf(stderr, "   '%s' at %f pt\n", node->text, node->size);
884         fprintf(stderr, "   align h = %s, v = %s,  sizep = %f, %f\n",
885                 (node->halign == GFX_H_RIGHT ? "r" :
886                         (node->halign == GFX_H_CENTER ? "c" :
887                                 (node->halign == GFX_H_LEFT ? "l" : "N"))),
888                 (node->valign == GFX_V_TOP ? "t" :
889                         (node->valign == GFX_V_CENTER ? "c" :
890                                 (node->valign == GFX_V_BOTTOM ? "b" : "N"))),
891                         g->sizep.x, g->sizep.y);
892         fprintf(stderr, "   r = %f = %f, cos = %f, sin = %f\n",
893                         g->r, node->angle, g->cos_r, g->sin_r);
894         fprintf(stderr, "   ascender = %f, descender = %f, baselineY = %f\n",
895                 g->ascender, g->descender, g->baselineY);
896         fprintf(stderr, "   sizep: %f, %f\n", g->sizep.x, g->sizep.y);
897         fprintf(stderr, "   minp: %f, %f     maxp = %f, %f\n", 
898                         g->minp.x, g->minp.y, g->maxp.x, g->maxp.y);
899         fprintf(stderr, "   x = %f, y = %f\n", g->x, g->y);
900         fprintf(stderr, "   tdx = %f, tdy = %f\n", g->tdx, g->tdy);
901         fprintf(stderr, "   GM = %f, %f, %f, %f, %f, %f\n",
902                         g->ma, g->mb, g->mc, g->md, g->mx, g->my);
903         fprintf(stderr, "   TM = %f, %f, %f, %f, %f, %f\n",
904                         g->ma, g->mb, g->mc, g->md, g->tmx, g->tmy);
905 }
906 #endif
907  
908 #if PDF_CALC_DEBUG
909 #define PDF_DD(x) if (g->debug) x;
910 #else
911 #define PDF_DD(x)
912 #endif
913
914 static void pdf_rotate(pdf_coords *g, pdf_point *p)
915 {
916     double x2 = g->cos_r * p->x - g->sin_r * p->y;
917     double y2 = g->sin_r * p->x + g->cos_r * p->y;
918         PDF_DD( fprintf(stderr, "  rotate(%f, %f) -> %f, %f\n", p->x, p->y, x2, y2))
919     p->x = x2;
920         p->y = y2;
921 }
922
923
924 static void pdf_calc(int page_height, gfx_node_t *node, pdf_coords *g)
925 {
926         pdf_point a, b, c;
927 #if PDF_CALC_DEBUG
928         /* g->debug = !!strstr(node->text, "RevProxy-1") || !!strstr(node->text, "08:00"); */
929         g->debug = !!strstr(node->text, "sekunder") || !!strstr(node->text, "Web");
930 #endif
931         g->x = node->x;
932         g->y = page_height - node->y;
933         if (node->angle) {
934                 g->r = 2 * M_PI * node->angle / 360.0;
935                 g->cos_r = cos(g->r);
936                 g->sin_r = sin(g->r);
937         } else {
938                 g->r = 0;
939                 g->cos_r = 1;
940                 g->sin_r = 0;
941         }
942         g->ascender = afm_get_ascender(node->filename, node->size);
943         g->descender = afm_get_descender(node->filename, node->size);
944         g->sizep.x = afm_get_text_width(0, node->filename, node->size, node->tabwidth, node->text);
945         /* seems like libart ignores the descender when doing vertial-align = bottom,
946            so we do that too, to get labels v-aligning properly */
947         g->sizep.y = -g->ascender; /* + afm_get_descender(font->ps_font, node->size); */
948         g->baselineY = -g->ascender - g->sizep.y / 2;
949         a.x = g->sizep.x; a.y = g->sizep.y;
950         b.x = g->sizep.x; b.y = 0;
951         c.x = 0; c.y = g->sizep.y;
952         if (node->angle) {
953                 pdf_rotate(g, &a);
954                 pdf_rotate(g, &b);
955                 pdf_rotate(g, &c);
956         }
957         g->minp.x = min3(a.x, b.x, c.x);
958         g->minp.y = min3(a.y, b.y, c.y);
959         g->maxp.x = max3(a.x, b.x, c.x);
960         g->maxp.y = max3(a.y, b.y, c.y);
961   /* The alignment parameters in node->valign and node->halign
962      specifies the alignment in the non-rotated coordinate system
963      (very unlike pdf/postscript), which complicates matters.
964   */
965         switch (node->halign) {
966         case GFX_H_RIGHT:  g->tdx = -g->maxp.x; break;
967         case GFX_H_CENTER: g->tdx = -(g->maxp.x + g->minp.x) / 2; break;
968         case GFX_H_LEFT:   g->tdx = -g->minp.x; break;
969         case GFX_H_NULL:   g->tdx = 0; break;
970         }
971         switch(node->valign){
972         case GFX_V_TOP:    g->tdy = -g->maxp.y; break;
973         case GFX_V_CENTER: g->tdy = -(g->maxp.y + g->minp.y) / 2; break;
974         case GFX_V_BOTTOM: g->tdy = -g->minp.y; break;
975         case GFX_V_NULL:   g->tdy = 0; break;          
976         }
977         g->ma = g->cos_r;
978         g->mb = g->sin_r;
979         g->mc = -g->sin_r;
980         g->md = g->cos_r;
981         g->mx = g->x + g->tdx;
982         g->my = g->y + g->tdy;
983         g->tmx = g->mx - g->ascender * g->mc;
984         g->tmy = g->my - g->ascender * g->md;
985         PDF_DD(pdf_dump_calc(node, g))
986 }
987
988 /* ------- SVG -------
989    SVG reference:
990    http://www.w3.org/TR/SVG/
991 */
992 static int svg_indent = 0;
993 static int svg_single_line = 0;
994 static const char *svg_default_font = "-dummy-";
995 typedef struct svg_dash
996 {
997   int dash_enable;
998   double dash_adjust, dash_len, dash_offset;
999   double adjusted_on, adjusted_off;
1000 } svg_dash;
1001
1002
1003 static void svg_print_indent(FILE *fp)
1004 {
1005   int i;
1006    for (i = svg_indent - svg_single_line; i > 0; i--) {
1007      putc(' ', fp);
1008      putc(' ', fp);
1009    }
1010 }
1011  
1012 static void svg_start_tag(FILE *fp, const char *name)
1013 {
1014    svg_print_indent(fp);
1015    putc('<', fp);
1016    fputs(name, fp);
1017    svg_indent++;
1018 }
1019  
1020 static void svg_close_tag_single_line(FILE *fp)
1021 {
1022    svg_single_line++;
1023    putc('>', fp);
1024 }
1025  
1026 static void svg_close_tag(FILE *fp)
1027 {
1028    putc('>', fp);
1029    if (!svg_single_line)
1030      putc('\n', fp);
1031 }
1032  
1033 static void svg_end_tag(FILE *fp, const char *name)
1034 {
1035    /* name is NULL if closing empty-node tag */
1036    svg_indent--;
1037    if (svg_single_line)
1038      svg_single_line--;
1039    else if (name)
1040      svg_print_indent(fp);
1041    if (name != NULL) {
1042      fputs("</", fp);
1043      fputs(name, fp);
1044    } else {
1045      putc('/', fp);
1046    }
1047    svg_close_tag(fp);
1048 }
1049  
1050 static void svg_close_tag_empty_node(FILE *fp)
1051 {
1052    svg_end_tag(fp, NULL);
1053 }
1054  
1055 static void svg_write_text(FILE *fp, const char *text)
1056 {
1057 #ifdef HAVE_MBSTOWCS
1058     size_t clen;
1059     wchar_t *p, *cstr, ch;
1060     int text_count;
1061     if (!text)
1062         return;
1063     clen = strlen(text) + 1;
1064     cstr = malloc(sizeof(wchar_t) * clen);
1065     text_count = mbstowcs(cstr, text, clen);
1066     if (text_count == -1)
1067         text_count = mbstowcs(cstr, "Enc-Err", 6);
1068     p = cstr;
1069 #else
1070     unsigned char *p = text;
1071     unsigned char *cstr;
1072     char ch;
1073     if (!p)
1074         return;
1075 #endif
1076   while (1) {
1077     ch = *p++;
1078     ch = afm_fix_osx_charset(ch); /* unsafe macro */
1079     switch (ch) {
1080     case 0:
1081 #ifdef HAVE_MBSTOWCS
1082     free(cstr);
1083 #endif
1084     return;
1085     case '&': fputs("&amp;", fp); break;
1086     case '<': fputs("&lt;", fp); break;
1087     case '>': fputs("&gt;", fp); break;
1088     case '"': fputs("&quot;", fp); break;
1089     default:
1090         if (ch == 32) {
1091 #ifdef HAVE_MBSTOWCS
1092             if (p <= cstr + 1 || !*p || *p == 32)
1093                 fputs("&#160;", fp); /* non-breaking space in unicode */
1094             else
1095 #endif
1096                 fputc(32, fp);
1097         } else if (ch < 32 || ch >= 127)
1098         fprintf(fp, "&#%d;", (int)ch);
1099       else
1100         putc((char)ch, fp);
1101      }
1102    }
1103 }
1104  
1105 static void svg_format_number(char *buf, int bufsize, double d)
1106 {
1107    /* omit decimals if integer to reduce filesize */
1108    char *p;
1109    snprintf(buf, bufsize, "%.2f", d);
1110    p = buf; /* doesn't trust snprintf return value */
1111    while (*p)
1112      p++;
1113    while (--p > buf) {
1114      char ch = *p;
1115      if (ch == '0') {
1116        *p = '\0'; /* zap trailing zeros */
1117        continue;
1118      }
1119      if (ch == '.')
1120        *p = '\0'; /* zap trailing dot */
1121      break;
1122    }
1123 }
1124  
1125 static void svg_write_number(FILE *fp, double d)
1126 {
1127    char buf[60];
1128    svg_format_number(buf, sizeof(buf), d);
1129    fputs(buf, fp);
1130 }
1131
1132 static int svg_color_is_black(int c)
1133 {
1134   /* gfx_color_t is RRGGBBAA */
1135   return c == 0x000000FF;
1136 }
1137  
1138 static void svg_write_color(FILE *fp, gfx_color_t c, const char *attr)
1139 {
1140   /* gfx_color_t is RRGGBBAA, svg can use #RRGGBB and #RGB like html */
1141   gfx_color_t rrggbb = (int)((c >> 8) & 0xFFFFFF);
1142   gfx_color_t opacity = c & 0xFF;
1143   fprintf(fp, " %s=\"", attr);
1144   if ((rrggbb & 0x0F0F0F) == ((rrggbb >> 4) & 0x0F0F0F)) {
1145      /* css2 short form, #rgb is #rrggbb, not #r0g0b0 */
1146     fprintf(fp, "#%03lX",
1147           ( ((rrggbb >> 8) & 0xF00)
1148           | ((rrggbb >> 4) & 0x0F0)
1149           | ( rrggbb       & 0x00F)));
1150    } else {
1151     fprintf(fp, "#%06lX", rrggbb);
1152    }
1153   fputs("\"", fp);
1154   if (opacity != 0xFF) {
1155     fprintf(fp, " opacity=\"");
1156     svg_write_number(fp, opacity / 255.0);
1157     fputs("\"", fp);
1158  }
1159 }
1160  
1161 static void svg_get_dash(gfx_node_t *node, svg_dash *d)
1162 {
1163   double offset;
1164   int mult;
1165   if (node->dash_on <= 0 || node->dash_off <= 0) {
1166     d->dash_enable = 0;
1167     return;
1168   }
1169   d->dash_enable = 1;
1170   d->dash_len = node->dash_on + node->dash_off;
1171   /* dash on/off adjustment due to round caps */
1172   d->dash_adjust = 0.8 * node->size;
1173   d->adjusted_on = node->dash_on - d->dash_adjust;
1174   if (d->adjusted_on < 0.01)
1175       d->adjusted_on = 0.01;
1176   d->adjusted_off = d->dash_len - d->adjusted_on;
1177   /* dash offset calc */
1178   if (node->path[0].x == node->path[1].x) /* only good for horz/vert lines */
1179     offset = node->path[0].y;
1180   else
1181     offset = node->path[0].x;
1182   mult = (int)fabs(offset / d->dash_len);
1183   d->dash_offset = offset - mult * d->dash_len;
1184   if (node->path[0].x < node->path[1].x || node->path[0].y < node->path[1].y)
1185     d->dash_offset = d->dash_len - d->dash_offset;
1186 }
1187
1188 static int svg_dash_equal(svg_dash *a, svg_dash *b)
1189 {
1190   if (a->dash_enable != b->dash_enable)
1191     return 0;
1192   if (a->adjusted_on != b->adjusted_on)
1193     return 0;
1194   if (a->adjusted_off != b->adjusted_off)
1195     return 0;
1196   /* rest of properties will be the same when on+off are */
1197   return 1;
1198 }
1199
1200 static void svg_common_path_attributes(FILE *fp, gfx_node_t *node)
1201 {
1202   svg_dash dash_info;
1203   svg_get_dash(node, &dash_info);
1204   fputs(" stroke-width=\"", fp);
1205   svg_write_number(fp, node->size);
1206   fputs("\"", fp);
1207   svg_write_color(fp, node->color, "stroke");
1208   fputs(" fill=\"none\"", fp);
1209   if (dash_info.dash_enable) {
1210     if (dash_info.dash_offset != 0) {
1211       fputs(" stroke-dashoffset=\"", fp);
1212       svg_write_number(fp, dash_info.dash_offset);
1213       fputs("\"", fp);
1214     }
1215     fputs(" stroke-dasharray=\"", fp);
1216     svg_write_number(fp, dash_info.adjusted_on);
1217     fputs(",", fp);
1218     svg_write_number(fp, dash_info.adjusted_off);
1219     fputs("\"", fp);
1220   }
1221 }
1222
1223 static int svg_is_int_step(double a, double b)
1224 {
1225    double diff = fabs(a - b);
1226    return floor(diff) == diff;
1227 }
1228  
1229 static int svg_path_straight_segment(FILE *fp,
1230      double lastA, double currentA, double currentB,
1231      gfx_node_t *node,
1232      int segment_idx, int isx, char absChar, char relChar)
1233 {
1234    if (!svg_is_int_step(lastA, currentA)) {
1235      putc(absChar, fp);
1236      svg_write_number(fp, currentA);
1237      return 0;
1238    }
1239    if (segment_idx < node->points - 1) {
1240      ArtVpath *vec = node->path + segment_idx + 1;
1241      if (vec->code == ART_LINETO) {
1242        double nextA = (isx ? vec->x : vec->y) - LINEOFFSET;
1243        double nextB = (isx ? vec->y : vec->x) - LINEOFFSET;
1244        if (nextB == currentB
1245            && ((currentA >= lastA) == (nextA >= currentA))
1246            && svg_is_int_step(currentA, nextA)) {
1247          return 1; /* skip to next as it is a straight line  */
1248        }
1249      }
1250    }
1251    putc(relChar, fp);
1252    svg_write_number(fp, currentA - lastA);
1253    return 0;
1254 }
1255  
1256 static void svg_path(FILE *fp, gfx_node_t *node, int multi)
1257 {
1258    int i;
1259    double lastX = 0, lastY = 0;
1260    /* for straight lines <path..> tags take less space than
1261       <line..> tags because of the efficient packing
1262       in the 'd' attribute */
1263    svg_start_tag(fp, "path");
1264   if (!multi)
1265     svg_common_path_attributes(fp, node);
1266    fputs(" d=\"", fp);
1267    /* specification of the 'd' attribute: */
1268    /* http://www.w3.org/TR/SVG/paths.html#PathDataGeneralInformation */
1269    for (i = 0; i < node->points; i++) {
1270      ArtVpath *vec = node->path + i;
1271      double x = vec->x - LINEOFFSET;
1272      double y = vec->y - LINEOFFSET;
1273      switch (vec->code) {
1274      case ART_MOVETO_OPEN: /* fall-through */
1275      case ART_MOVETO:
1276        putc('M', fp);
1277        svg_write_number(fp, x);
1278        putc(',', fp);
1279        svg_write_number(fp, y);
1280        break;
1281      case ART_LINETO:
1282        /* try optimize filesize by using minimal lineto commands */
1283        /* without introducing rounding errors. */
1284        if (x == lastX) {
1285          if (svg_path_straight_segment(fp, lastY, y, x, node, i, 0, 'V', 'v'))
1286            continue;
1287        } else if (y == lastY) {
1288          if (svg_path_straight_segment(fp, lastX, x, y, node, i, 1, 'H', 'h'))
1289            continue;
1290        } else {
1291          putc('L', fp);
1292          svg_write_number(fp, x);
1293          putc(',', fp);
1294          svg_write_number(fp, y);
1295        }
1296        break;
1297      case ART_CURVETO: break; /* unsupported */
1298      case ART_END: break; /* nop */
1299      }
1300      lastX = x;
1301      lastY = y;
1302    }
1303   if (node->closed_path)
1304     fputs(" Z", fp);
1305    fputs("\"", fp);
1306    svg_close_tag_empty_node(fp);
1307 }
1308  
1309 static void svg_multi_path(FILE *fp, gfx_node_t **nodeR)
1310 {
1311    /* optimize for multiple paths with the same color, penwidth, etc. */
1312    int num = 1;
1313    gfx_node_t *node = *nodeR;
1314    gfx_node_t *next = node->next;
1315    while (next) {
1316      if (next->type != node->type
1317          || next->size != node->size
1318         || next->color != node->color
1319         || next->dash_on != node->dash_on
1320         || next->dash_off != node->dash_off)
1321        break;
1322      next = next->next;
1323      num++;
1324    }
1325    if (num == 1) {
1326      svg_path(fp, node, 0);
1327      return;
1328    }
1329    svg_start_tag(fp, "g");
1330   svg_common_path_attributes(fp, node);
1331    svg_close_tag(fp);
1332    while (num && node) {
1333      svg_path(fp, node, 1);
1334      if (!--num)
1335        break;
1336      node = node->next;
1337      *nodeR = node;
1338    }
1339    svg_end_tag(fp, "g");
1340 }
1341  
1342 static void svg_area(FILE *fp, gfx_node_t *node)
1343 {
1344    int i;
1345    double startX = 0, startY = 0;
1346    svg_start_tag(fp, "polygon");
1347   fputs(" ", fp);
1348   svg_write_color(fp, node->color, "fill");
1349   fputs(" points=\"", fp);
1350    for (i = 0; i < node->points; i++) {
1351      ArtVpath *vec = node->path + i;
1352      double x = vec->x - LINEOFFSET;
1353      double y = vec->y - LINEOFFSET;
1354      switch (vec->code) {
1355        case ART_MOVETO_OPEN: /* fall-through */
1356        case ART_MOVETO:
1357          svg_write_number(fp, x);
1358          putc(',', fp);
1359          svg_write_number(fp, y);
1360          startX = x;
1361          startY = y;
1362          break;
1363        case ART_LINETO:
1364          if (i == node->points - 2
1365                         && node->path[i + 1].code == ART_END
1366              && fabs(x - startX) < 0.001 && fabs(y - startY) < 0.001) {
1367            break; /* poly area always closed, no need for last point */
1368          }
1369          putc(' ', fp);
1370          svg_write_number(fp, x);
1371          putc(',', fp);
1372          svg_write_number(fp, y);
1373          break;
1374        case ART_CURVETO: break; /* unsupported */
1375        case ART_END: break; /* nop */
1376      }
1377    }
1378    fputs("\"", fp);
1379    svg_close_tag_empty_node(fp);
1380 }
1381  
1382 static void svg_text(FILE *fp, gfx_node_t *node)
1383 {
1384    pdf_coords g;
1385    const char *fontname;
1386    /* as svg has 0,0 in top-left corner (like most screens) instead of
1387           bottom-left corner like pdf and eps, we have to fake the coords
1388           using offset and inverse sin(r) value */
1389    int page_height = 1000;
1390    pdf_calc(page_height, node, &g);
1391    if (node->angle != 0) {
1392      svg_start_tag(fp, "g");
1393          /* can't use svg_write_number as 2 decimals is far from enough to avoid
1394                 skewed text */
1395      fprintf(fp, " transform=\"matrix(%f,%f,%f,%f,%f,%f)\"",
1396                          g.ma, -g.mb, -g.mc, g.md, g.tmx, page_height - g.tmy);
1397      svg_close_tag(fp);
1398    }
1399    svg_start_tag(fp, "text");
1400    if (!node->angle) {
1401      fputs(" x=\"", fp);
1402      svg_write_number(fp, g.tmx);
1403      fputs("\" y=\"", fp);
1404      svg_write_number(fp, page_height - g.tmy);
1405      fputs("\"", fp);
1406    }
1407    fontname = afm_get_font_name(node->filename);
1408    if (strcmp(fontname, svg_default_font))
1409      fprintf(fp, " font-family=\"%s\"", fontname);
1410    fputs(" font-size=\"", fp);
1411    svg_write_number(fp, node->size);
1412    fputs("\"", fp);
1413   if (!svg_color_is_black(node->color))
1414     svg_write_color(fp, node->color, "fill");
1415    svg_close_tag_single_line(fp);
1416    /* support for node->tabwidth missing */
1417    svg_write_text(fp, node->text);
1418    svg_end_tag(fp, "text");
1419    if (node->angle != 0)
1420      svg_end_tag(fp, "g");
1421 }
1422  
1423 int       gfx_render_svg (gfx_canvas_t *canvas,
1424                  art_u32 width, art_u32 height,
1425                  gfx_color_t background, FILE *fp){
1426    gfx_node_t *node = canvas->firstnode;
1427    /* Find the first font used, and assume it is the mostly used
1428           one. It reduces the number of font-familty attributes. */
1429    while (node) {
1430            if (node->type == GFX_TEXT && node->filename) {
1431                    svg_default_font = afm_get_font_name(node->filename);
1432                    break;
1433            }
1434            node = node->next;
1435    }
1436    fputs(
1437 "<?xml version=\"1.0\" standalone=\"no\"?>\n"
1438 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n"
1439 "   \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
1440 "<!--\n"
1441 "   SVG file created by\n"
1442 "        RRDtool " PACKAGE_VERSION " Tobias Oetiker, http://tobi.oetiker.ch\n"
1443 "\n"
1444 "   The width/height attributes in the outhermost svg node\n"
1445 "   are just default sizes for the browser which is used\n"
1446 "   if the svg file is openened directly without being\n"
1447 "   embedded in an html file.\n"
1448 "   The viewBox is the local coord system for rrdtool.\n"
1449 "-->\n", fp);
1450    svg_start_tag(fp, "svg");
1451    fputs(" width=\"", fp);
1452    svg_write_number(fp, width * canvas->zoom);
1453    fputs("\" height=\"", fp);
1454    svg_write_number(fp, height * canvas->zoom);
1455    fputs("\" x=\"0\" y=\"0\" viewBox=\"", fp);
1456    svg_write_number(fp, -LINEOFFSET);
1457    fputs(" ", fp);
1458    svg_write_number(fp, -LINEOFFSET);
1459    fputs(" ", fp);
1460    svg_write_number(fp, width - LINEOFFSET);
1461    fputs(" ", fp);
1462    svg_write_number(fp, height - LINEOFFSET);
1463    fputs("\" preserveAspectRatio=\"xMidYMid\"", fp);
1464    fprintf(fp, " font-family=\"%s\"", svg_default_font); /* default font */
1465    fputs(" stroke-linecap=\"round\" stroke-linejoin=\"round\"", fp);
1466    fputs(" xmlns=\"http://www.w3.org/2000/svg\"", fp);
1467    fputs(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"", fp);
1468    svg_close_tag(fp);
1469    svg_start_tag(fp, "rect");
1470    fprintf(fp, " x=\"0\" y=\"0\" width=\"%d\" height=\"%d\"", width, height);
1471   svg_write_color(fp, background, "fill");
1472    svg_close_tag_empty_node(fp);
1473    node = canvas->firstnode;
1474    while (node) {
1475      switch (node->type) {
1476      case GFX_LINE:
1477        svg_multi_path(fp, &node);
1478        break;
1479      case GFX_AREA:
1480        svg_area(fp, node);
1481        break;
1482      case GFX_TEXT:
1483        svg_text(fp, node);
1484      }
1485      node = node->next;
1486    }
1487    svg_end_tag(fp, "svg");
1488    return 0;
1489 }
1490
1491 /* ------- EPS -------
1492    EPS and Postscript references:
1493    http://partners.adobe.com/asn/developer/technotes/postscript.html
1494 */
1495
1496 typedef struct eps_font
1497 {
1498   const char *ps_font;
1499   int id;
1500   struct eps_font *next;
1501 } eps_font;
1502
1503 typedef struct eps_state
1504 {
1505   FILE *fp;
1506   gfx_canvas_t *canvas;
1507   art_u32 page_width, page_height;
1508   eps_font *font_list;
1509   /*--*/
1510   gfx_color_t color;
1511   const char *font;
1512   double font_size;
1513   double line_width;
1514   int linecap, linejoin;
1515   int has_dash;
1516 } eps_state;
1517
1518 static void eps_set_color(eps_state *state, gfx_color_t color)
1519 {
1520 #if USE_EPS_FAKE_ALPHA
1521    double a1, a2;
1522 #endif
1523    /* gfx_color_t is RRGGBBAA */
1524   if (state->color == color)
1525     return;
1526 #if USE_EPS_FAKE_ALPHA
1527   a1 = (color & 255) / 255.0;
1528   a2 = 255 * (1 - a1);
1529 #define eps_color_calc(x) (int)( ((x) & 255) * a1 + a2)
1530 #else
1531 #define eps_color_calc(x) (int)( (x) & 255)
1532 #endif
1533    /* gfx_color_t is RRGGBBAA */
1534   if (state->color == color)
1535     return;
1536   fprintf(state->fp, "%d %d %d Rgb\n",
1537       eps_color_calc(color >> 24),
1538       eps_color_calc(color >> 16),
1539       eps_color_calc(color >>  8));
1540   state->color = color;
1541 }
1542
1543 static int eps_add_font(eps_state *state, gfx_node_t *node)
1544 {
1545   /* The fonts list could be postponed to the end using
1546      (atend), but let's be nice and have them in the header. */
1547   const char *ps_font = afm_get_font_postscript_name(node->filename);
1548   eps_font *ef;
1549   for (ef = state->font_list; ef; ef = ef->next) {
1550     if (!strcmp(ps_font, ef->ps_font))
1551       return 0;
1552   }
1553   ef = malloc(sizeof(eps_font));
1554   if (ef == NULL) {
1555     rrd_set_error("malloc for eps_font");
1556     return -1;
1557   }
1558   ef->next = state->font_list;
1559   ef->ps_font = ps_font;
1560   state->font_list = ef;
1561   return 0;
1562 }
1563
1564 static void eps_list_fonts(eps_state *state, const char *dscName)
1565 {
1566   eps_font *ef;
1567   int lineLen = strlen(dscName);
1568   if (!state->font_list)
1569     return;
1570   fputs(dscName, state->fp);
1571   for (ef = state->font_list; ef; ef = ef->next) {
1572     int nameLen = strlen(ef->ps_font);
1573     if (lineLen + nameLen > 100 && lineLen) {
1574       fputs("\n", state->fp);
1575       fputs("%%- \n", state->fp);
1576       lineLen = 5;
1577     } else {
1578       fputs(" ", state->fp);
1579       lineLen++;
1580     }
1581     fputs(ef->ps_font, state->fp);
1582     lineLen += nameLen;
1583   }
1584   fputs("\n", state->fp);
1585 }
1586
1587 static void eps_define_fonts(eps_state *state)
1588 {
1589   eps_font *ef;
1590   if (!state->font_list)
1591     return;
1592   for (ef = state->font_list; ef; ef = ef->next) {
1593     /* PostScript¨ LANGUAGE REFERENCE third edition
1594        page 349 */
1595     fprintf(state->fp,
1596         "%%\n"
1597         "/%s findfont dup length dict begin\n"
1598         "{ 1 index /FID ne {def} {pop pop} ifelse } forall\n"
1599         "/Encoding ISOLatin1Encoding def\n"
1600         "currentdict end\n"
1601         "/%s-ISOLatin1 exch definefont pop\n"
1602         "/SetFont-%s { /%s-ISOLatin1 findfont exch scalefont setfont } bd\n",
1603         ef->ps_font, ef->ps_font, ef->ps_font, ef->ps_font);
1604   }
1605 }
1606
1607 static int eps_prologue(eps_state *state)
1608 {
1609   gfx_node_t *node;
1610   fputs(
1611     "%!PS-Adobe-3.0 EPSF-3.0\n"
1612     "%%Creator: RRDtool " PACKAGE_VERSION " Tobias Oetiker, http://tobi.oetiker.ch\n"
1613     /* can't like weird chars here */
1614     "%%Title: (RRDtool output)\n"
1615     "%%DocumentData: Clean7Bit\n"
1616     "", state->fp);
1617   fprintf(state->fp, "%%%%BoundingBox: 0 0 %d %d\n",
1618     state->page_width, state->page_height);
1619   for (node = state->canvas->firstnode; node; node = node->next) {
1620     if (node->type == GFX_TEXT && eps_add_font(state, node) == -1)
1621       return -1;
1622   }
1623   eps_list_fonts(state, "%%DocumentFonts:");
1624   eps_list_fonts(state, "%%DocumentNeededFonts:");
1625   fputs(
1626       "%%EndComments\n"
1627       "%%BeginProlog\n"
1628       "%%EndProlog\n" /* must have, or BoundingBox is ignored */
1629       "/bd { bind def } bind def\n"
1630       "", state->fp);
1631   fprintf(state->fp, "/X { %.2f add } bd\n", LINEOFFSET);
1632   fputs(
1633       "/X2 {X exch X exch} bd\n"
1634       "/M {X2 moveto} bd\n"
1635       "/L {X2 lineto} bd\n"
1636       "/m {moveto} bd\n"
1637       "/l {lineto} bd\n"
1638       "/S {stroke} bd\n"
1639       "/CP {closepath} bd\n"
1640       "/WS {setlinewidth stroke} bd\n"
1641       "/F {fill} bd\n"
1642       "/T1 {gsave} bd\n"
1643       "/T2 {concat 0 0 moveto show grestore} bd\n"
1644       "/T   {moveto show} bd\n"
1645       "/Rgb { 255.0 div 3 1 roll\n"
1646       "       255.0 div 3 1 roll \n"
1647       "       255.0 div 3 1 roll setrgbcolor } bd\n"
1648       "", state->fp);
1649   eps_define_fonts(state);
1650   return 0;
1651 }
1652
1653 static void eps_clear_dash(eps_state *state)
1654 {
1655   if (!state->has_dash)
1656     return;
1657   state->has_dash = 0;
1658   fputs("[1 0] 0 setdash\n", state->fp);
1659 }
1660
1661 static void eps_write_linearea(eps_state *state, gfx_node_t *node)
1662 {
1663   int i;
1664   FILE *fp = state->fp;
1665   int useOffset = 0;
1666   int clearDashIfAny = 1;
1667   eps_set_color(state, node->color);
1668   if (node->type == GFX_LINE) {
1669     svg_dash dash_info;
1670     if (state->linecap != 1) {
1671       fputs("1 setlinecap\n", fp);
1672       state->linecap = 1;
1673     }
1674     if (state->linejoin != 1) {
1675       fputs("1 setlinejoin\n", fp);
1676       state->linejoin = 1;
1677     }
1678     svg_get_dash(node, &dash_info);
1679     if (dash_info.dash_enable) {
1680       clearDashIfAny = 0;
1681       state->has_dash = 1;
1682       fputs("[", fp);
1683       svg_write_number(fp, dash_info.adjusted_on);
1684       fputs(" ", fp);
1685       svg_write_number(fp, dash_info.adjusted_off);
1686       fputs("] ", fp);
1687       svg_write_number(fp, dash_info.dash_offset);
1688       fputs(" setdash\n", fp);
1689     }
1690   }
1691   if (clearDashIfAny)
1692     eps_clear_dash(state);
1693   for (i = 0; i < node->points; i++) {
1694     ArtVpath *vec = node->path + i;
1695     double x = vec->x;
1696     double y = state->page_height - vec->y;
1697     if (vec->code == ART_MOVETO_OPEN || vec->code == ART_MOVETO)
1698       useOffset = (fabs(x - floor(x) - 0.5) < 0.01 && fabs(y - floor(y) - 0.5) < 0.01);
1699     if (useOffset) {
1700       x -= LINEOFFSET;
1701       y -= LINEOFFSET;
1702     }
1703     switch (vec->code) {
1704     case ART_MOVETO_OPEN: /* fall-through */
1705     case ART_MOVETO:
1706       svg_write_number(fp, x);
1707       fputc(' ', fp);
1708       svg_write_number(fp, y);
1709       fputc(' ', fp);
1710       fputs(useOffset ? "M\n" : "m\n", fp);
1711       break;
1712     case ART_LINETO:
1713       svg_write_number(fp, x);
1714       fputc(' ', fp);
1715       svg_write_number(fp, y);
1716       fputc(' ', fp);
1717       fputs(useOffset ? "L\n" : "l\n", fp);
1718       break;
1719     case ART_CURVETO: break; /* unsupported */
1720     case ART_END: break; /* nop */
1721     }
1722   }
1723   if (node->type == GFX_LINE) {
1724     if (node->closed_path)
1725       fputs("CP ", fp);
1726     if (node->size != state->line_width) {
1727       state->line_width = node->size;
1728       svg_write_number(fp, state->line_width);
1729       fputs(" WS\n", fp);
1730     } else {
1731       fputs("S\n", fp);
1732     }
1733    } else {
1734     fputs("F\n", fp);
1735    }
1736 }
1737
1738 static void eps_write_text(eps_state *state, gfx_node_t *node)
1739 {
1740   FILE *fp = state->fp;
1741   const char *ps_font = afm_get_font_postscript_name(node->filename);
1742   int lineLen = 0;
1743   pdf_coords g;
1744 #ifdef HAVE_MBSTOWCS
1745     size_t clen;
1746     wchar_t *p, *cstr, ch;
1747     int text_count;
1748     if (!node->text)
1749         return;
1750     clen = strlen(node->text) + 1;
1751     cstr = malloc(sizeof(wchar_t) * clen);
1752     text_count = mbstowcs(cstr, node->text, clen);
1753     if (text_count == -1)
1754         text_count = mbstowcs(cstr, "Enc-Err", 6);
1755     p = cstr;
1756 #else
1757     const unsigned char *p = node->text;
1758     unsigned char ch;
1759     if (!p)
1760         return;
1761 #endif
1762   pdf_calc(state->page_height, node, &g);
1763   eps_set_color(state, node->color);
1764   if (strcmp(ps_font, state->font) || node->size != state->font_size) {
1765     state->font = ps_font;
1766     state->font_size = node->size;
1767     svg_write_number(fp, state->font_size);
1768     fprintf(fp, " SetFont-%s\n", state->font);
1769   }
1770   if (node->angle)
1771           fputs("T1 ", fp);
1772   fputs("(", fp);
1773   lineLen = 20;
1774   while (1) {
1775     ch = *p;
1776     if (!ch)
1777       break;
1778         ch = afm_fix_osx_charset(ch); /* unsafe macro */
1779     if (++lineLen > 70) {
1780       fputs("\\\n", fp); /* backslash and \n */
1781       lineLen = 0;
1782     }
1783     switch (ch) {
1784       case '%':
1785       case '(':
1786       case ')':
1787       case '\\':
1788         fputc('\\', fp);
1789         fputc(ch, fp);
1790         break;
1791       case '\n':
1792         fputs("\\n", fp);
1793         break;
1794       case '\r':
1795         fputs("\\r", fp);
1796         break;
1797       case '\t':
1798         fputs("\\t", fp);
1799         break;
1800       default:
1801         if (ch > 255) {
1802             fputc('?', fp);
1803         } else if (ch >= 126 || ch < 32) {
1804           fprintf(fp, "\\%03o", (unsigned int)ch);
1805           lineLen += 3;
1806         } else {
1807           fputc(ch, fp);
1808         }
1809       }
1810       p++;
1811   }
1812 #ifdef HAVE_MBSTOWCS
1813   free(cstr);
1814 #endif
1815   if (node->angle) {
1816          /* can't use svg_write_number as 2 decimals is far from enough to avoid
1817                 skewed text */
1818           fprintf(fp, ") [%f %f %f %f %f %f] T2\n",
1819                           g.ma, g.mb, g.mc, g.md, g.tmx, g.tmy);
1820   } else {
1821           fputs(") ", fp);
1822           svg_write_number(fp, g.tmx);
1823           fputs(" ", fp);
1824           svg_write_number(fp, g.tmy);
1825           fputs(" T\n", fp);
1826   }
1827 }
1828
1829 static int eps_write_content(eps_state *state)
1830 {
1831   gfx_node_t *node;
1832   fputs("%\n", state->fp);
1833   for (node = state->canvas->firstnode; node; node = node->next) {
1834     switch (node->type) {
1835     case GFX_LINE:
1836     case GFX_AREA:
1837       eps_write_linearea(state, node);
1838       break;
1839     case GFX_TEXT:
1840       eps_write_text(state, node);
1841       break;
1842     }
1843   }
1844   return 0;
1845 }
1846
1847 int       gfx_render_eps (gfx_canvas_t *canvas,
1848                  art_u32 width, art_u32 height,
1849                  gfx_color_t background, FILE *fp){
1850   struct eps_state state;
1851   state.fp = fp;
1852   state.canvas = canvas;
1853   state.page_width = width;
1854   state.page_height = height;
1855   state.font = "no-default-font";
1856   state.font_size = -1;
1857   state.color = 0; /* black */
1858   state.font_list = NULL;
1859   state.linecap = -1;
1860   state.linejoin = -1;
1861   state.has_dash = 0;
1862   state.line_width = 1;
1863   if (eps_prologue(&state) == -1)
1864     return -1;
1865   eps_set_color(&state, background);
1866   fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
1867       height, width, height, width);
1868   if (eps_write_content(&state) == -1)
1869     return 0;
1870   fputs("showpage\n", fp);
1871   fputs("%%EOF\n", fp);
1872   while (state.font_list) {
1873     eps_font *next = state.font_list->next;
1874     free(state.font_list);
1875     state.font_list = next;
1876   }
1877   return 0;
1878 }
1879
1880 /* ------- PDF -------
1881    PDF references page:
1882    http://partners.adobe.com/public/developer/pdf/index_reference.html
1883 */
1884
1885 typedef struct pdf_buffer
1886 {
1887   int id, is_obj, is_dict, is_stream, pdf_file_pos;
1888   char *data;
1889   int alloc_size, current_size;
1890   struct pdf_buffer *previous_buffer, *next_buffer;
1891   struct pdf_state *state;
1892 } pdf_buffer;
1893
1894 typedef struct pdf_font
1895 {
1896   const char *ps_font;
1897   pdf_buffer obj;
1898   struct pdf_font *next;
1899 } pdf_font;
1900
1901 typedef struct pdf_state
1902 {
1903   FILE *fp;
1904   gfx_canvas_t *canvas;
1905   art_u32 page_width, page_height;
1906   pdf_font *font_list;
1907   pdf_buffer *first_buffer, *last_buffer;
1908   int pdf_file_pos;
1909   int has_failed;
1910   /*--*/
1911   gfx_color_t stroke_color, fill_color;
1912   int font_id;
1913   double font_size;
1914   double line_width;
1915   svg_dash dash;
1916   int linecap, linejoin;
1917   int last_obj_id;
1918   /*--*/
1919   pdf_buffer pdf_header;
1920   pdf_buffer info_obj, catalog_obj, pages_obj, page1_obj;
1921   pdf_buffer fontsdict_obj;
1922   pdf_buffer graph_stream;
1923 } pdf_state;
1924
1925 static void pdf_init_buffer(pdf_state *state, pdf_buffer *buf)
1926 {
1927   int initial_size = 32;
1928   buf->state = state;
1929   buf->id = -42;
1930   buf->alloc_size = 0;
1931   buf->current_size = 0;
1932   buf->data = (char*)malloc(initial_size);
1933   buf->is_obj = 0;
1934   buf->previous_buffer = NULL;
1935   buf->next_buffer = NULL;
1936   if (buf->data == NULL) {
1937     rrd_set_error("malloc for pdf_buffer data");
1938     state->has_failed = 1;
1939     return;
1940   }
1941   buf->alloc_size = initial_size;
1942   if (state->last_buffer)
1943     state->last_buffer->next_buffer = buf;
1944   if (state->first_buffer == NULL)
1945     state->first_buffer = buf;
1946   buf->previous_buffer = state->last_buffer;
1947   state->last_buffer = buf;
1948 }
1949
1950 static void pdf_put(pdf_buffer *buf, const char *text, int len)
1951 {
1952   if (len <= 0)
1953     return;
1954   if (buf->alloc_size < buf->current_size + len) {
1955     int new_size = buf->alloc_size;
1956     char *new_buf;
1957     while (new_size < buf->current_size + len)
1958       new_size *= 4;
1959     new_buf = (char*)malloc(new_size);
1960     if (new_buf == NULL) {
1961       rrd_set_error("re-malloc for pdf_buffer data");
1962       buf->state->has_failed = 1;
1963       return;
1964     }
1965     memcpy(new_buf, buf->data, buf->current_size);
1966     free(buf->data);
1967     buf->data = new_buf;
1968     buf->alloc_size = new_size;
1969   }
1970   memcpy(buf->data + buf->current_size, text, len);
1971   buf->current_size += len;
1972 }
1973
1974 static void pdf_put_char(pdf_buffer *buf, char c)
1975 {
1976     if (buf->alloc_size >= buf->current_size + 1) {
1977         buf->data[buf->current_size++] = c;
1978     } else {
1979         char tmp[1];
1980         tmp[0] = (char)c;
1981         pdf_put(buf, tmp, 1);
1982     }
1983 }
1984
1985 static void pdf_puts(pdf_buffer *buf, const char *text)
1986 {
1987   pdf_put(buf, text, strlen(text));
1988 }
1989
1990 static void pdf_indent(pdf_buffer *buf)
1991 {
1992   pdf_puts(buf, "\t");
1993 }
1994
1995 static void pdf_putsi(pdf_buffer *buf, const char *text)
1996 {
1997   pdf_indent(buf);
1998   pdf_puts(buf, text);
1999 }
2000
2001 static void pdf_putint(pdf_buffer *buf, int i)
2002 {
2003   char tmp[20];
2004   sprintf(tmp, "%d", i);
2005   pdf_puts(buf, tmp);
2006 }
2007
2008 static void pdf_putnumber(pdf_buffer *buf, double d)
2009 {
2010   char tmp[50];
2011   svg_format_number(tmp, sizeof(tmp), d);
2012   pdf_puts(buf, tmp);
2013 }
2014
2015 static void pdf_put_string_contents_wide(pdf_buffer *buf, const afm_char *text)
2016 {
2017     const afm_char *p = text;
2018     while (1) {
2019         afm_char ch = *p;
2020         ch = afm_fix_osx_charset(ch); /* unsafe macro */
2021         switch (ch) {
2022             case 0:
2023                 return;
2024             case '(':
2025                 pdf_puts(buf, "\\(");
2026                 break;
2027             case ')':
2028                 pdf_puts(buf, "\\)");
2029                 break;
2030             case '\\':
2031                 pdf_puts(buf, "\\\\");
2032                 break;
2033             case '\n':
2034                 pdf_puts(buf, "\\n");
2035                 break;
2036             case '\r':
2037                 pdf_puts(buf, "\\r");
2038                 break;
2039             case '\t':
2040                 pdf_puts(buf, "\\t");
2041                 break;
2042             default:
2043                 if (ch > 255) {
2044                     pdf_put_char(buf, '?');
2045                 } else if (ch > 125 || ch < 32) {
2046                     pdf_put_char(buf, ch);
2047                 } else {
2048                     char tmp[10];
2049                     snprintf(tmp, sizeof(tmp), "\\%03o", (int)ch);
2050                     pdf_puts(buf, tmp);
2051                 }
2052         }
2053         p++;
2054     }
2055 }
2056
2057 static void pdf_put_string_contents(pdf_buffer *buf, const char *text)
2058 {
2059 #ifdef HAVE_MBSTOWCS
2060     size_t clen = strlen(text) + 1;
2061     wchar_t *cstr = malloc(sizeof(wchar_t) * clen);
2062     int text_count = mbstowcs(cstr, text, clen);
2063     if (text_count == -1)
2064         text_count = mbstowcs(cstr, "Enc-Err", 6);
2065     pdf_put_string_contents_wide(buf, cstr);
2066 #if 0
2067     if (*text == 'W') {
2068         fprintf(stderr, "Decoding utf8 for '%s'\n", text);
2069         wchar_t *p = cstr;
2070         char *pp = text;
2071         fprintf(stderr, "sz wc = %d\n", sizeof(wchar_t));
2072         while (*p) {
2073             fprintf(stderr, "  %d = %c  versus %d = %c\n", *p, (char)*p, 255 & (int)*pp, *pp);
2074             p++;
2075             pp++;
2076         }
2077     }
2078 #endif
2079     free(cstr);
2080 #else
2081     pdf_put_string_contents_wide(buf, text);
2082 #endif
2083 }
2084
2085 static void pdf_init_object(pdf_state *state, pdf_buffer *buf)
2086 {
2087   pdf_init_buffer(state, buf);
2088   buf->id = ++state->last_obj_id;
2089   buf->is_obj = 1;
2090   buf->is_stream = 0;
2091 }
2092
2093 static void pdf_init_dict(pdf_state *state, pdf_buffer *buf)
2094 {
2095   pdf_init_object(state, buf);
2096   buf->is_dict = 1;
2097 }
2098
2099 static void pdf_set_color(pdf_buffer *buf, gfx_color_t color,
2100         gfx_color_t *current_color, const char *op)
2101 {
2102 #if USE_PDF_FAKE_ALPHA
2103    double a1, a2;
2104 #endif
2105    /* gfx_color_t is RRGGBBAA */
2106   if (*current_color == color)
2107     return;
2108 #if USE_PDF_FAKE_ALPHA
2109   a1 = (color & 255) / 255.0;
2110   a2 = 1 - a1;
2111 #define pdf_color_calc(x) ( ((x)  & 255) / 255.0 * a1 + a2)
2112 #else
2113 #define pdf_color_calc(x) ( ((x)  & 255) / 255.0)
2114 #endif
2115   pdf_putnumber(buf, pdf_color_calc(color >> 24));
2116   pdf_puts(buf, " ");
2117   pdf_putnumber(buf, pdf_color_calc(color >> 16));
2118   pdf_puts(buf, " ");
2119   pdf_putnumber(buf, pdf_color_calc(color >>  8));
2120   pdf_puts(buf, " ");
2121   pdf_puts(buf, op);
2122   pdf_puts(buf, "\n");
2123   *current_color = color;
2124 }
2125
2126 static void pdf_set_stroke_color(pdf_buffer *buf, gfx_color_t color)
2127 {
2128     pdf_set_color(buf, color, &buf->state->stroke_color, "RG");
2129 }
2130
2131 static void pdf_set_fill_color(pdf_buffer *buf, gfx_color_t color)
2132 {
2133     pdf_set_color(buf, color, &buf->state->fill_color, "rg");
2134 }
2135
2136 static pdf_font *pdf_find_font(pdf_state *state, gfx_node_t *node)
2137 {
2138   const char *ps_font = afm_get_font_postscript_name(node->filename);
2139   pdf_font *ef;
2140   for (ef = state->font_list; ef; ef = ef->next) {
2141     if (!strcmp(ps_font, ef->ps_font))
2142       return ef;
2143   }
2144   return NULL;
2145 }
2146
2147 static void pdf_add_font(pdf_state *state, gfx_node_t *node)
2148 {
2149   pdf_font *ef = pdf_find_font(state, node);
2150   if (ef)
2151     return;
2152   ef = malloc(sizeof(pdf_font));
2153   if (ef == NULL) {
2154     rrd_set_error("malloc for pdf_font");
2155     state->has_failed = 1;
2156     return;
2157   }
2158   pdf_init_dict(state, &ef->obj);
2159   ef->next = state->font_list;
2160   ef->ps_font = afm_get_font_postscript_name(node->filename);
2161   state->font_list = ef;
2162   /* fonts dict */
2163   pdf_putsi(&state->fontsdict_obj, "/F");
2164   pdf_putint(&state->fontsdict_obj, ef->obj.id);
2165   pdf_puts(&state->fontsdict_obj, " ");
2166   pdf_putint(&state->fontsdict_obj, ef->obj.id);
2167   pdf_puts(&state->fontsdict_obj, " 0 R\n");
2168   /* fonts def */
2169   pdf_putsi(&ef->obj, "/Type /Font\n");
2170   pdf_putsi(&ef->obj, "/Subtype /Type1\n");
2171   pdf_putsi(&ef->obj, "/Name /F");
2172   pdf_putint(&ef->obj, ef->obj.id);
2173   pdf_puts(&ef->obj, "\n");
2174   pdf_putsi(&ef->obj, "/BaseFont /");
2175   pdf_puts(&ef->obj, ef->ps_font);
2176   pdf_puts(&ef->obj, "\n");
2177   pdf_putsi(&ef->obj, "/Encoding /WinAnsiEncoding\n");
2178   /*  'Cp1252' (this is latin 1 extended with 27 characters;
2179       the encoding is also known as 'winansi')
2180       http://www.lowagie.com/iText/tutorial/ch09.html */
2181 }
2182
2183 static void pdf_create_fonts(pdf_state *state)
2184 {
2185   gfx_node_t *node;
2186   for (node = state->canvas->firstnode; node; node = node->next) {
2187     if (node->type == GFX_TEXT)
2188       pdf_add_font(state, node);
2189   }
2190 }
2191
2192 static void pdf_write_linearea(pdf_state *state, gfx_node_t *node)
2193 {
2194   int i;
2195   pdf_buffer *s = &state->graph_stream;
2196   if (node->type == GFX_LINE) {
2197     svg_dash dash_info;
2198     svg_get_dash(node, &dash_info);
2199     if (!svg_dash_equal(&dash_info, &state->dash)) {
2200       state->dash = dash_info;
2201       if (dash_info.dash_enable) {
2202         pdf_puts(s, "[");
2203         pdf_putnumber(s, dash_info.adjusted_on);
2204         pdf_puts(s, " ");
2205         pdf_putnumber(s, dash_info.adjusted_off);
2206         pdf_puts(s, "] ");
2207         pdf_putnumber(s, dash_info.dash_offset);
2208         pdf_puts(s, " d\n");
2209       } else {
2210         pdf_puts(s, "[] 0 d\n");
2211       }
2212     }
2213     pdf_set_stroke_color(s, node->color);
2214     if (state->linecap != 1) {
2215       pdf_puts(s, "1 j\n");
2216       state->linecap = 1;
2217     }
2218     if (state->linejoin != 1) {
2219       pdf_puts(s, "1 J\n");
2220       state->linejoin = 1;
2221     }
2222     if (node->size != state->line_width) {
2223       state->line_width = node->size;
2224       pdf_putnumber(s, state->line_width);
2225       pdf_puts(s, " w\n");
2226     }
2227   } else {
2228     pdf_set_fill_color(s, node->color);
2229   }
2230   for (i = 0; i < node->points; i++) {
2231     ArtVpath *vec = node->path + i;
2232     double x = vec->x;
2233     double y = state->page_height - vec->y;
2234     if (node->type == GFX_AREA) {
2235       x += LINEOFFSET; /* adjust for libart handling of areas */
2236       y -= LINEOFFSET;
2237     }
2238     switch (vec->code) {
2239     case ART_MOVETO_OPEN: /* fall-through */
2240     case ART_MOVETO:
2241       pdf_putnumber(s, x);
2242       pdf_puts(s, " ");
2243       pdf_putnumber(s, y);
2244       pdf_puts(s, " m\n");
2245       break;
2246     case ART_LINETO:
2247       pdf_putnumber(s, x);
2248       pdf_puts(s, " ");
2249       pdf_putnumber(s, y);
2250       pdf_puts(s, " l\n");
2251       break;
2252     case ART_CURVETO: break; /* unsupported */
2253     case ART_END: break; /* nop */
2254     }
2255   }
2256   if (node->type == GFX_LINE) {
2257     pdf_puts(s, node->closed_path ? "s\n" : "S\n");
2258    } else {
2259     pdf_puts(s, "f\n");
2260    }
2261 }
2262
2263
2264 static void pdf_write_matrix(pdf_state *state, gfx_node_t *node, pdf_coords *g, int useTM)
2265 {
2266         char tmp[150];
2267         pdf_buffer *s = &state->graph_stream;
2268         if (node->angle == 0) {
2269                 pdf_puts(s, "1 0 0 1 ");
2270                 pdf_putnumber(s, useTM ? g->tmx : g->mx);
2271                 pdf_puts(s, " ");
2272                 pdf_putnumber(s, useTM ? g->tmy : g->my);
2273         } else {
2274                  /* can't use svg_write_number as 2 decimals is far from enough to avoid
2275                         skewed text */
2276                 sprintf(tmp, "%f %f %f %f %f %f",
2277                                 g->ma, g->mb, g->mc, g->md, 
2278                                 useTM ? g->tmx : g->mx,
2279                                 useTM ? g->tmy : g->my);
2280                 pdf_puts(s, tmp);
2281         }
2282 }
2283
2284 static void pdf_write_text(pdf_state *state, gfx_node_t *node, 
2285     int last_was_text, int next_is_text)
2286 {
2287   pdf_coords g;
2288   pdf_buffer *s = &state->graph_stream;
2289   pdf_font *font = pdf_find_font(state, node);
2290   if (font == NULL) {
2291     rrd_set_error("font disappeared");
2292     state->has_failed = 1;
2293     return;
2294   }
2295   pdf_calc(state->page_height, node, &g);
2296 #if PDF_CALC_DEBUG
2297   pdf_puts(s, "q % debug green box\n");
2298   pdf_write_matrix(state, node, &g, 0);
2299   pdf_puts(s, " cm\n");
2300   pdf_set_fill_color(s, 0x90FF9000);
2301   pdf_puts(s, "0 0.4 0 rg\n");
2302   pdf_puts(s, "0 0 ");
2303   pdf_putnumber(s, g.sizep.x);
2304   pdf_puts(s, " ");
2305   pdf_putnumber(s, g.sizep.y);
2306   pdf_puts(s, " re\n");
2307   pdf_puts(s, "f\n");
2308   pdf_puts(s, "Q\n");
2309 #endif
2310   pdf_set_fill_color(s, node->color);
2311   if (PDF_CALC_DEBUG || !last_was_text)
2312     pdf_puts(s, "BT\n");
2313   if (state->font_id != font->obj.id || node->size != state->font_size) {
2314     state->font_id = font->obj.id;
2315     state->font_size = node->size;
2316     pdf_puts(s, "/F");
2317     pdf_putint(s, font->obj.id);
2318     pdf_puts(s, " ");
2319     pdf_putnumber(s, node->size);
2320     pdf_puts(s, " Tf\n");
2321   }
2322   pdf_write_matrix(state, node, &g, 1);
2323   pdf_puts(s, " Tm\n");
2324   pdf_puts(s, "(");
2325   pdf_put_string_contents(s, node->text);
2326   pdf_puts(s, ") Tj\n");
2327   if (PDF_CALC_DEBUG || !next_is_text)
2328     pdf_puts(s, "ET\n");
2329 }
2330  
2331 static void pdf_write_content(pdf_state *state)
2332 {
2333   gfx_node_t *node;
2334   int last_was_text = 0, next_is_text;
2335   for (node = state->canvas->firstnode; node; node = node->next) {
2336     switch (node->type) {
2337     case GFX_LINE:
2338     case GFX_AREA:
2339       pdf_write_linearea(state, node);
2340       break;
2341     case GFX_TEXT:
2342       next_is_text = node->next && node->next->type == GFX_TEXT;
2343       pdf_write_text(state, node, last_was_text, next_is_text);
2344       break;
2345     }
2346     last_was_text = node->type == GFX_TEXT;
2347   }
2348 }
2349
2350 static void pdf_init_document(pdf_state *state)
2351 {
2352   pdf_init_buffer(state, &state->pdf_header);
2353   pdf_init_dict(state, &state->catalog_obj);
2354   pdf_init_dict(state, &state->info_obj);
2355   pdf_init_dict(state, &state->pages_obj);
2356   pdf_init_dict(state, &state->page1_obj);
2357   pdf_init_dict(state, &state->fontsdict_obj);
2358   pdf_create_fonts(state);
2359   if (state->has_failed)
2360     return;
2361   /* make stream last object in file */
2362   pdf_init_object(state, &state->graph_stream);
2363   state->graph_stream.is_stream = 1;
2364 }
2365
2366 static void pdf_setup_document(pdf_state *state)
2367 {
2368   const char *creator = "RRDtool " PACKAGE_VERSION " Tobias Oetiker, http://tobi.oetiker.ch";
2369   /* all objects created by now, so init code can reference them */
2370   /* HEADER */
2371   pdf_puts(&state->pdf_header, "%PDF-1.3\n");
2372   /* following 8 bit comment is recommended by Adobe for
2373      indicating binary file to file transfer applications */
2374   pdf_puts(&state->pdf_header, "%\xE2\xE3\xCF\xD3\n");
2375   /* INFO */
2376   pdf_putsi(&state->info_obj, "/Creator (");
2377   pdf_put_string_contents(&state->info_obj, creator);
2378   pdf_puts(&state->info_obj, ")\n");
2379   /* CATALOG */
2380   pdf_putsi(&state->catalog_obj, "/Type /Catalog\n");
2381   pdf_putsi(&state->catalog_obj, "/Pages ");
2382   pdf_putint(&state->catalog_obj, state->pages_obj.id);
2383   pdf_puts(&state->catalog_obj, " 0 R\n");
2384   /* PAGES */
2385   pdf_putsi(&state->pages_obj, "/Type /Pages\n");
2386   pdf_putsi(&state->pages_obj, "/Kids [");
2387   pdf_putint(&state->pages_obj, state->page1_obj.id);
2388   pdf_puts(&state->pages_obj, " 0 R]\n");
2389   pdf_putsi(&state->pages_obj, "/Count 1\n");
2390   /* PAGE 1 */
2391   pdf_putsi(&state->page1_obj, "/Type /Page\n");
2392   pdf_putsi(&state->page1_obj, "/Parent ");
2393   pdf_putint(&state->page1_obj, state->pages_obj.id);
2394   pdf_puts(&state->page1_obj, " 0 R\n");
2395   pdf_putsi(&state->page1_obj, "/MediaBox [0 0 ");
2396   pdf_putint(&state->page1_obj, state->page_width);
2397   pdf_puts(&state->page1_obj, " ");
2398   pdf_putint(&state->page1_obj, state->page_height);
2399   pdf_puts(&state->page1_obj, "]\n");
2400   pdf_putsi(&state->page1_obj, "/Contents ");
2401   pdf_putint(&state->page1_obj, state->graph_stream.id);
2402   pdf_puts(&state->page1_obj, " 0 R\n");
2403   pdf_putsi(&state->page1_obj, "/Resources << /Font ");
2404   pdf_putint(&state->page1_obj, state->fontsdict_obj.id);
2405   pdf_puts(&state->page1_obj, " 0 R >>\n");
2406 }
2407
2408 static void pdf_write_string_to_file(pdf_state *state, const char *text)
2409 {
2410     fputs(text, state->fp);
2411     state->pdf_file_pos += strlen(text);
2412 }
2413
2414 static void pdf_write_buf_to_file(pdf_state *state, pdf_buffer *buf)
2415 {
2416   char tmp[40];
2417   buf->pdf_file_pos = state->pdf_file_pos;
2418   if (buf->is_obj) {
2419     snprintf(tmp, sizeof(tmp), "%d 0 obj\n", buf->id);
2420     pdf_write_string_to_file(state, tmp);
2421   }
2422   if (buf->is_dict)
2423     pdf_write_string_to_file(state, "<<\n");
2424   if (buf->is_stream) {
2425     snprintf(tmp, sizeof(tmp), "<< /Length %d >>\n", buf->current_size);
2426     pdf_write_string_to_file(state, tmp);
2427     pdf_write_string_to_file(state, "stream\n");
2428   }
2429   fwrite(buf->data, 1, buf->current_size, state->fp);
2430   state->pdf_file_pos += buf->current_size;
2431   if (buf->is_stream)
2432     pdf_write_string_to_file(state, "endstream\n");
2433   if (buf->is_dict)
2434     pdf_write_string_to_file(state, ">>\n");
2435   if (buf->is_obj)
2436     pdf_write_string_to_file(state, "endobj\n");
2437 }
2438
2439 static void pdf_write_to_file(pdf_state *state)
2440 {
2441   pdf_buffer *buf = state->first_buffer;
2442   int xref_pos;
2443   state->pdf_file_pos = 0;
2444   pdf_write_buf_to_file(state, &state->pdf_header);
2445   while (buf) {
2446     if (buf->is_obj)
2447       pdf_write_buf_to_file(state, buf);
2448     buf = buf->next_buffer;
2449   }
2450   xref_pos = state->pdf_file_pos;
2451   fprintf(state->fp, "xref\n");
2452   fprintf(state->fp, "%d %d\n", 0, state->last_obj_id + 1);
2453   /* TOC lines must be exactly 20 bytes including \n */
2454   fprintf(state->fp, "%010d %05d f\x20\n", 0, 65535);
2455   for (buf = state->first_buffer; buf; buf = buf->next_buffer) {
2456     if (buf->is_obj)
2457       fprintf(state->fp, "%010d %05d n\x20\n", buf->pdf_file_pos, 0);
2458   }
2459   fprintf(state->fp, "trailer\n");
2460   fprintf(state->fp, "<<\n");
2461   fprintf(state->fp, "\t/Size %d\n", state->last_obj_id + 1);
2462   fprintf(state->fp, "\t/Root %d 0 R\n", state->catalog_obj.id);
2463   fprintf(state->fp, "\t/Info %d 0 R\n", state->info_obj.id);
2464   fprintf(state->fp, ">>\n");
2465   fprintf(state->fp, "startxref\n");
2466   fprintf(state->fp, "%d\n", xref_pos);
2467   fputs("%%EOF\n", state->fp);
2468 }
2469
2470 static void pdf_free_resources(pdf_state *state)
2471 {
2472   pdf_buffer *buf = state->first_buffer;
2473   while (buf) {
2474     free(buf->data);
2475     buf->data = NULL;
2476     buf->alloc_size = buf->current_size = 0;
2477     buf = buf->next_buffer;
2478   }
2479   while (state->font_list) {
2480     pdf_font *next = state->font_list->next;
2481     free(state->font_list);
2482     state->font_list = next;
2483   }
2484 }
2485
2486 int       gfx_render_pdf (gfx_canvas_t *canvas,
2487                  art_u32 width, art_u32 height,
2488                  gfx_color_t UNUSED(background), FILE *fp){
2489   struct pdf_state state;
2490   memset(&state, 0, sizeof(pdf_state));
2491   state.fp = fp;
2492   state.canvas = canvas;
2493   state.page_width = width;
2494   state.page_height = height;
2495   state.font_id = -1;
2496   state.font_size = -1;
2497   state.font_list = NULL;
2498   state.linecap = -1;
2499   state.linejoin = -1;
2500   pdf_init_document(&state);
2501   /*
2502   pdf_set_color(&state, background);
2503   fprintf(fp, "0 0 M 0 %d L %d %d L %d 0 L fill\n",
2504       height, width, height, width);
2505   */
2506   if (!state.has_failed)
2507     pdf_write_content(&state);
2508   if (!state.has_failed)
2509     pdf_setup_document(&state);
2510   if (!state.has_failed)
2511     pdf_write_to_file(&state);
2512   pdf_free_resources(&state);
2513   return state.has_failed ? -1 : 0;
2514 }
2515