* fix transparency rendering by rewinding the paths propperly
[rrdtool.git] / src / rrd_gfx.c
index 28007cd..0a44344 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
+ * RRDtool 1.2rc2  Copyright by Tobi Oetiker, 1997-2005
  ****************************************************************************
  * rrd_gfx.c  graphics wrapper for rrdtool
   **************************************************************************/
@@ -15,6 +15,7 @@
 #include <png.h>
 #include <ft2build.h>
 #include FT_FREETYPE_H
+#include FT_GLYPH_H
 
 #include "rrd_gfx.h"
 #include "rrd_afm.h"
 /* lines are better drawn on the pixle than between pixles */
 #define LINEOFFSET 0.5
 
+typedef struct gfx_char_s *gfx_char;
+struct gfx_char_s {
+  FT_UInt     index;    /* glyph index */
+  FT_Vector   pos;      /* location from baseline in 26.6 */
+  FT_Glyph    image;    /* glyph bitmap */
+};
+
+typedef struct gfx_string_s *gfx_string;
+struct gfx_string_s {
+  unsigned int    width;
+  unsigned int    height;
+  size_t          count;  /* number of characters */
+  gfx_char        glyphs;
+  size_t          num_glyphs;
+  FT_BBox         bbox;
+  FT_Matrix       transform;
+};
+
+/* compute string bbox */
+static void compute_string_bbox(gfx_string string);
+
+/* create a freetype glyph string */
+gfx_string gfx_string_create ( FT_Face face,
+                               const char *text, int rotation, double tabwidth);
+
+/* create a freetype glyph string */
+static void gfx_string_destroy ( gfx_string string );
+
 static
 gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){
   gfx_node_t *node = art_new(gfx_node_t,1);
@@ -182,10 +211,6 @@ gfx_node_t   *gfx_new_text   (gfx_canvas_t *canvas,
                              enum gfx_v_align_en v_align,
                               char* text){
    gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
-   if (angle != 0.0){
-       /* currently we only support 0 and 270 */
-       angle = 270.0;
-   }
    
    node->text = strdup(text);
    node->size = size;
@@ -235,12 +260,23 @@ int           gfx_render(gfx_canvas_t *canvas,
   }
 }
 
+static void gfx_string_destroy ( gfx_string string ) {
+  unsigned int n;
+  if (string->glyphs) {
+    for (n=0; n<string->num_glyphs; ++n)
+      FT_Done_Glyph (string->glyphs[n].image);
+    free (string->glyphs);
+  }
+  free (string);
+}
+
+
 double gfx_get_text_width ( gfx_canvas_t *canvas,
                            double start, char* font, double size,
-                           double tabwidth, char* text){
+                           double tabwidth, char* text, int rotation){
   switch (canvas->imgformat) {
   case IF_PNG: 
-    return gfx_get_text_width_libart (canvas, start, font, size, tabwidth, text);
+    return gfx_get_text_width_libart (start, font, size, tabwidth, text, rotation);
   case IF_SVG: /* fall through */ 
   case IF_EPS:
   case IF_PDF:
@@ -250,56 +286,30 @@ double gfx_get_text_width ( gfx_canvas_t *canvas,
   }
 }
 
-double gfx_get_text_width_libart ( gfx_canvas_t *canvas,
+double gfx_get_text_width_libart (
                            double start, char* font, double size,
-                           double tabwidth, char* text){
+                           double tabwidth, char* text, int rotation){
 
-  FT_GlyphSlot  slot;
-  FT_UInt       previous=0;
-  FT_UInt       glyph_index=0;
-  FT_Bool       use_kerning;
   int           error;
+  double        text_width=0;
   FT_Face       face;
   FT_Library    library=NULL;  
-  double        text_width=0;
+  gfx_string    string;
+
   FT_Init_FreeType( &library );
   error = FT_New_Face( library, font, 0, &face );
   if ( error ) return -1;
   error = FT_Set_Char_Size(face,  size*64,size*64,  100,100);
   if ( error ) return -1;
 
-  use_kerning = FT_HAS_KERNING(face);
-  slot = face->glyph;
-  for(;*text;text++) { 
-    previous = glyph_index;
-    glyph_index = FT_Get_Char_Index( face, *text);
-    
-    if (use_kerning && previous && glyph_index){
-      FT_Vector  delta;
-      FT_Get_Kerning( face, previous, glyph_index,
-                     0, &delta );
-      text_width += (double)delta.x / 64.0;
-      
-    }
-    error = FT_Load_Glyph( face, glyph_index, 0 );
-    if ( error ) {
-      FT_Done_FreeType(library);
-      return -1;
-    }
-    if (! previous) {
-      text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */       
-    }
-    text_width += (double)slot->metrics.horiAdvance / 64.0;
-  }
-  text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
-  text_width += (double)slot->metrics.width / 64.0; /* add just char width */
-  text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
+  string = gfx_string_create( face, text, rotation,tabwidth);
+  text_width = string->width;
+  gfx_string_destroy(string);
   FT_Done_FreeType(library);
-  return text_width;
+  return text_width/64;
 }
-static void gfx_libart_close_path(gfx_canvas_t *canvas,
-       gfx_node_t *node, ArtVpath **vec)
+
+static void gfx_libart_close_path(gfx_node_t *node, ArtVpath **vec)
 {
     /* libart must have end==start for closed paths,
        even if using ART_MOVETO and not ART_MOVETO_OPEN
@@ -311,8 +321,7 @@ static void gfx_libart_close_path(gfx_canvas_t *canvas,
     art_vpath_add_point (vec, &points, &points_max, ART_END, 0, 0);
 }
 
-static void gfx_round_scaled_coordinates(gfx_canvas_t *canvas,
-       gfx_node_t *node, ArtVpath *vec)
+static void gfx_round_scaled_coordinates(ArtVpath *vec)
 {
     while (vec->code != ART_END) {
        vec->x = floor(vec->x - LINEOFFSET + 0.5) + LINEOFFSET;
@@ -321,9 +330,173 @@ static void gfx_round_scaled_coordinates(gfx_canvas_t *canvas,
     }
 }
 
+/* find bbox of a string */
+static void compute_string_bbox(gfx_string string) {
+    unsigned int n;
+    FT_BBox bbox;
+
+    bbox.xMin = bbox.yMin = 32000;
+    bbox.xMax = bbox.yMax = -32000;
+    for ( n = 0; n < string->num_glyphs; n++ ) {
+      FT_BBox glyph_bbox;
+      FT_Glyph_Get_CBox( string->glyphs[n].image, ft_glyph_bbox_gridfit,
+       &glyph_bbox );
+      if (glyph_bbox.xMin < bbox.xMin) {
+         bbox.xMin = glyph_bbox.xMin;
+      }
+      if (glyph_bbox.yMin < bbox.yMin) {
+        bbox.yMin = glyph_bbox.yMin;
+      }
+      if (glyph_bbox.xMax > bbox.xMax) {
+         bbox.xMax = glyph_bbox.xMax;
+      }
+      if (glyph_bbox.yMax > bbox.yMax) {
+         bbox.yMax = glyph_bbox.yMax;
+      }
+    }
+    if ( bbox.xMin > bbox.xMax ) { 
+      bbox.xMin = 0;
+      bbox.yMin = 0;
+      bbox.xMax = 0;
+      bbox.yMax = 0;
+    }
+    string->bbox.xMin = bbox.xMin;
+    string->bbox.xMax = bbox.xMax;
+    string->bbox.yMin = bbox.yMin;
+    string->bbox.yMax = bbox.yMax;
+} 
+
+/* create a free type glyph string */
+gfx_string gfx_string_create(FT_Face face,const char *text,
+        int rotation, double tabwidth)
+{
+
+  FT_GlyphSlot  slot = face->glyph;  /* a small shortcut */
+  FT_Bool       use_kerning;
+  FT_UInt       previous;
+  FT_Vector     ft_pen;
+
+  gfx_string    string;
+  gfx_char      glyph;          /* current glyph in table */
+  unsigned int  n;
+  int           error;
+  int        gottab = 0;    
+
+  ft_pen.x = 0;   /* start at (0,0) !! */
+  ft_pen.y = 0;
+
+  string = (gfx_string) malloc (sizeof(struct gfx_string_s));
+  string->width = 0;
+  string->height = 0;
+  string->count = strlen (text);
+  string->glyphs = (gfx_char) calloc (string->count,sizeof(struct gfx_char_s));
+  string->num_glyphs = 0;
+  string->transform.xx = (FT_Fixed)( cos(M_PI*(rotation)/180.0)*0x10000);
+  string->transform.xy = (FT_Fixed)(-sin(M_PI*(rotation)/180.0)*0x10000);
+  string->transform.yx = (FT_Fixed)( sin(M_PI*(rotation)/180.0)*0x10000);
+  string->transform.yy = (FT_Fixed)( cos(M_PI*(rotation)/180.0)*0x10000);
+
+  use_kerning = FT_HAS_KERNING(face);
+  previous    = 0;
+  glyph = string->glyphs;
+  for (n=0; n<string->count; n++, glyph++) {
+    FT_Vector   vec;
+    /* handle the tabs ...
+       have a witespace glyph inserted, but set its width such that the distance
+    of the new right edge is x times tabwidth from 0,0 where x is an integer. */    
+    char letter = text[n];
+    gottab = 0;
+    if (letter == '\\' && n+1 < string->count && text[n+1] == 't'){
+            /* we have a tab here so skip the backslash and
+               set t to ' ' so that we get a white space */
+            gottab = 1;
+            n++;
+            letter  = ' ';            
+    }            
+    if (letter == '\t'){
+       letter = ' ';
+        gottab = 1 ;
+    }            
+    /* initialize each struct gfx_char_s */
+    glyph->index = 0;
+    glyph->pos.x = 0;
+    glyph->pos.y = 0;
+    glyph->image = NULL;
+
+    glyph->index = FT_Get_Char_Index( face, letter );
+
+    /* compute glyph origin */
+    if ( use_kerning && previous && glyph->index ) {
+      FT_Vector kerning;
+      FT_Get_Kerning (face, previous, glyph->index,
+          ft_kerning_default, &kerning);
+      ft_pen.x += kerning.x;
+      ft_pen.y += kerning.y;
+    }
+
+    /* load the glyph image (in its native format) */
+    /* for now, we take a monochrome glyph bitmap */
+    error = FT_Load_Glyph (face, glyph->index, FT_LOAD_DEFAULT);
+    if (error) {
+      fprintf (stderr, "couldn't load glyph:  %c\n", letter);
+      continue;
+    }
+    error = FT_Get_Glyph (slot, &glyph->image);
+    if (error) {
+      fprintf (stderr, "couldn't get glyph from slot:  %c\n", letter);
+      continue;
+    }
+    /* if we are in tabbing mode, we replace the tab with a space and shift the position
+       of the space so that its left edge is where the tab was supposed to land us */
+    if (gottab){
+       /* we are in gridfitting mode so the calculations happen in 1/64 pixles */
+        ft_pen.x = tabwidth*64.0 * (float)(1 + (long)(ft_pen.x / (tabwidth * 64.0))) - slot->advance.x;
+    }
+    /* store current pen position */
+    glyph->pos.x = ft_pen.x;
+    glyph->pos.y = ft_pen.y;
+
+
+    ft_pen.x   += slot->advance.x;    
+    ft_pen.y   += slot->advance.y;
+
+    /* rotate glyph */
+    vec = glyph->pos;
+    FT_Vector_Transform (&vec, &string->transform);
+    error = FT_Glyph_Transform (glyph->image, &string->transform, &vec);
+    if (error) {
+      fprintf (stderr, "couldn't transform glyph\n");
+      continue;
+    }
+
+    /* convert to a bitmap - destroy native image */
+    error = FT_Glyph_To_Bitmap (&glyph->image, FT_RENDER_MODE_NORMAL, 0, 1);
+    if (error) {
+      fprintf (stderr, "couldn't convert glyph to bitmap\n");
+      continue;
+    }
+
+    /* increment number of glyphs */
+    previous = glyph->index;
+    string->num_glyphs++;
+  }
+/*  printf ("number of glyphs = %d\n", string->num_glyphs);*/
+  compute_string_bbox( string );
+  /* the last character was a tab */  
+  if (gottab) {
+      string->width = ft_pen.x;
+  } else {
+      string->width = string->bbox.xMax - string->bbox.xMin;
+  }
+  string->height = string->bbox.yMax - string->bbox.yMin;
+  return string;
+}
+
+
 static int gfx_save_png (art_u8 *buffer, FILE *fp,
                      long width, long height, long bytes_per_pixel);
 /* render grafics into png image */
+
 int           gfx_render_png (gfx_canvas_t *canvas, 
                              art_u32 width, art_u32 height, 
                              gfx_color_t background, FILE *fp){
@@ -344,37 +517,41 @@ int           gfx_render_png (gfx_canvas_t *canvas,
         switch (node->type) {
         case GFX_LINE:
         case GFX_AREA: {   
-            ArtVpath *vec;
+            ArtVpath *vec,*pvec;
             double dst[6];     
-            ArtSVP *svp;
+            ArtSVP *svp,*usvp,*rsvp;
             art_affine_scale(dst,canvas->zoom,canvas->zoom);
             vec = art_vpath_affine_transform(node->path,dst);
            if (node->closed_path)
-               gfx_libart_close_path(canvas, node, &vec);
-           gfx_round_scaled_coordinates(canvas, node, vec);
+               gfx_libart_close_path(node, &vec);
+           gfx_round_scaled_coordinates(vec);
+            pvec = art_vpath_perturb(vec);
+           art_free(vec);
             if(node->type == GFX_LINE){
-                svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
+                svp = art_svp_vpath_stroke ( pvec, ART_PATH_STROKE_JOIN_ROUND,
                                              ART_PATH_STROKE_CAP_ROUND,
                                              node->size*canvas->zoom,1,1);
             } else {
-                svp = art_svp_from_vpath ( vec );
+                svp = art_svp_from_vpath ( pvec );
             }
-            art_free(vec);
-            art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
-                               node->color, buffer, rowstride, NULL);
+            art_free(pvec);
+            usvp=art_svp_uncross(svp);
             art_free(svp);
+           rsvp=art_svp_rewind_uncrossed(usvp,ART_WIND_RULE_ODDEVEN); 
+            art_free(usvp); 
+            art_rgb_svp_alpha (rsvp ,0,0, pys_width, pys_height,
+                               node->color, buffer, rowstride, NULL);
+            art_free(rsvp);
             break;
         }
         case GFX_TEXT: {
+            unsigned int  n;
             int  error;
-            float text_width=0.0, text_height = 0.0;
-            unsigned char *text;
             art_u8 fcolor[3],falpha;
             FT_Face       face;
-            FT_GlyphSlot  slot;
-            FT_UInt       previous=0;
-            FT_UInt       glyph_index=0;
-           FT_Bool       use_kerning;
+            gfx_char      glyph;
+            gfx_string    string;
+            FT_Vector     vec;  /* 26.6 */
 
             float pen_x = 0.0 , pen_y = 0.0;
             /* double x,y; */
@@ -389,7 +566,6 @@ int           gfx_render_png (gfx_canvas_t *canvas,
                                  0,
                                  &face );
            if ( error ) break;
-            use_kerning = FT_HAS_KERNING(face);
 
             error = FT_Set_Char_Size(face,   /* handle to face object            */
                                      (long)(node->size*64),
@@ -399,75 +575,68 @@ int           gfx_render_png (gfx_canvas_t *canvas,
             if ( error ) break;
             pen_x = node->x * canvas->zoom;
             pen_y = node->y * canvas->zoom;
-            slot = face->glyph;
-
-            for(text=(unsigned char *)node->text;*text;text++) {       
-                previous = glyph_index;
-                glyph_index = FT_Get_Char_Index( face, *text);
-                
-                if (use_kerning && previous && glyph_index){
-                    FT_Vector  delta;
-                    FT_Get_Kerning( face, previous, glyph_index,
-                                    0, &delta );
-                    text_width += (double)delta.x / 64.0;
-                    
-                }
-                error = FT_Load_Glyph( face, glyph_index, 0 );
-                if ( error ) break;
-               if (previous == 0){
-                 pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */   
-                 text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */    
-                }
-               if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
-                 text_height = (double)slot->metrics.horiBearingY / 64.0;
-               }
-                text_width += (double)slot->metrics.horiAdvance / 64.0;
-            }
-            text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
-            text_width += (double)slot->metrics.width / 64.0; /* add just char width */
-            text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
-            
+
+            string = gfx_string_create (face, node->text, node->angle, node->tabwidth);
             switch(node->halign){
-            case GFX_H_RIGHT:  pen_x -= text_width; break;
-            case GFX_H_CENTER: pen_x -= text_width / 2.0; break;          
-            case GFX_H_LEFT: break;          
-            case GFX_H_NULL: break;          
+            case GFX_H_RIGHT:  vec.x = -string->bbox.xMax;
+                               break;          
+            case GFX_H_CENTER: vec.x = abs(string->bbox.xMax) >= abs(string->bbox.xMin) ?
+                                       -string->bbox.xMax/2:-string->bbox.xMin/2;
+                               break;          
+            case GFX_H_LEFT:   vec.x = -string->bbox.xMin;
+                              break;
+            case GFX_H_NULL:   vec.x = 0;
+                               break;          
             }
 
             switch(node->valign){
-            case GFX_V_TOP:    pen_y += text_height; break;
-            case GFX_V_CENTER: pen_y += text_height / 2.0; break;          
-            case GFX_V_BOTTOM: break;          
-            case GFX_V_NULL: break;          
+            case GFX_V_TOP:    vec.y = string->bbox.yMax;
+                               break;
+            case GFX_V_CENTER: vec.y = abs(string->bbox.yMax) >= abs(string->bbox.yMin) ?
+                                       string->bbox.yMax/2:string->bbox.yMin/2;
+                               break;
+            case GFX_V_BOTTOM: vec.y = 0;
+                               break;
+            case GFX_V_NULL:   vec.y = 0;
+                               break;
             }
-
-            glyph_index=0;
-            for(text=(unsigned char *)node->text;*text;text++) {
-                int gr;          
-                previous = glyph_index;
-                glyph_index = FT_Get_Char_Index( face, *text);
-                
-                if (use_kerning && previous && glyph_index){
-                    FT_Vector  delta;
-                    FT_Get_Kerning( face, previous, glyph_index,
-                                    0, &delta );
-                    pen_x += (double)delta.x / 64.0;
-                    
+           pen_x += vec.x/64;
+           pen_y += vec.y/64;
+            glyph = string->glyphs;
+            for(n=0; n<string->num_glyphs; ++n, ++glyph) {
+                int gr;
+                FT_Glyph        image;
+                FT_BitmapGlyph  bit;
+
+               /* make copy to transform */
+                if (! glyph->image) {
+                  fprintf (stderr, "no image\n");
+                  continue;
+                }
+                error = FT_Glyph_Copy (glyph->image, &image);
+                if (error) {
+                  fprintf (stderr, "couldn't copy image\n");
+                  continue;
                 }
-                error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
-                if ( error ) break;
-                gr = slot->bitmap.num_grays -1;
-                for (iy=0; iy < slot->bitmap.rows; iy++){
-                    long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
-                    if (buf_y < 0 || buf_y >= pys_height) continue;
+
+                /* transform it */
+                vec = glyph->pos;
+                FT_Vector_Transform (&vec, &string->transform);
+
+                bit = (FT_BitmapGlyph) image;
+
+                gr = bit->bitmap.num_grays -1;
+                for (iy=0; iy < bit->bitmap.rows; iy++){
+                    long buf_y = iy+(pen_y+0.5)-bit->top;
+                    if (buf_y < 0 || buf_y >= (long)pys_height) continue;
                     buf_y *= rowstride;
-                    for (ix=0;ix < slot->bitmap.width;ix++){
-                        long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
+                    for (ix=0;ix < bit->bitmap.width;ix++){
+                        long buf_x = ix + (pen_x + 0.5) + (double)bit->left ;
                         art_u8 font_alpha;
                         
-                        if (buf_x < 0 || buf_x >= pys_width) continue;
+                        if (buf_x < 0 || buf_x >= (long)pys_width) continue;
                         buf_x *=  bytes_per_pixel ;
-                        font_alpha =  *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
+                        font_alpha =  *(bit->bitmap.buffer + iy * bit->bitmap.width + ix);
                         font_alpha =  (art_u8)((double)font_alpha / gr * falpha);
                         for (iz = 0; iz < 3; iz++){
                             art_u8 *orig = buffer + buf_y + buf_x + iz;
@@ -476,8 +645,9 @@ int           gfx_render_png (gfx_canvas_t *canvas,
                         }
                     }
                 }
-                pen_x += (double)slot->metrics.horiAdvance / 64.0;
+                FT_Done_Glyph (image);
             }
+            gfx_string_destroy(string);
         }
         }
         node = node->next;
@@ -551,12 +721,19 @@ static int gfx_save_png (art_u8 *buffer, FILE *fp,  long width, long height, lon
   text[0].compression = PNG_TEXT_COMPRESSION_NONE;
   png_set_text (png_ptr, info_ptr, text, 1);
 
+  /* lets make this fast */
+  png_set_compression_level(png_ptr,1);
+  png_set_filter(png_ptr,PNG_FILTER_TYPE_BASE,PNG_NO_FILTERS);
+  /* 
+  png_set_filter(png_ptr,PNG_FILTER_TYPE_BASE,PNG_FILTER_SUB);
+  png_set_compression_strategy(png_ptr,Z_HUFFMAN_ONLY);
+  png_set_compression_level(png_ptr,Z_BEST_SPEED); */
+  
   /* Write header data */
   png_write_info (png_ptr, info_ptr);
-
   for (i = 0; i < height; i++)
     row_pointers[i] = (png_bytep) (buffer + i*rowstride);
-
+  
   png_write_image(png_ptr, row_pointers);
   png_write_end(png_ptr, info_ptr);
   png_destroy_write_struct(&png_ptr, &info_ptr);
@@ -972,8 +1149,11 @@ static void svg_text(FILE *fp, gfx_node_t *node)
    svg_write_number(fp, x);
    fputs("\" y=\"", fp);
    svg_write_number(fp, y);
-  if (strcmp(node->filename, svg_default_font))
+
+/*  if (strcmp(node->filename, svg_default_font))
     fprintf(fp, " font-family=\"%s\"", node->filename);
+    */
+   fputs("\" font-family=\"Helvetica", fp);
    fputs("\" font-size=\"", fp);
    svg_write_number(fp, node->size);
    fputs("\"", fp);
@@ -1157,9 +1337,9 @@ static int eps_prologue(eps_state *state)
   gfx_node_t *node;
   fputs(
     "%!PS-Adobe-3.0 EPSF-3.0\n"
-    "%%Creator: RRDtool 1.1.x, Tobias Oetiker, http://tobi.oetiker.ch\n"
+    "%%Creator: RRDtool 1.2rc2 Tobias Oetiker, http://tobi.oetiker.ch\n"
     /* can't like weird chars here */
-    "%%Title: (RRDTool output)\n"
+    "%%Title: (RRDtool output)\n"
     "%%DocumentData: Clean7Bit\n"
     "", state->fp);
   fprintf(state->fp, "%%%%BoundingBox: 0 0 %d %d\n",