--font-render-mode is back, and there is also
[rrdtool.git] / src / rrd_gfx.c
index 1a45439..8a18ce4 100644 (file)
 /****************************************************************************
- * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
+ * RRDtool 1.2.23  Copyright by Tobi Oetiker, 1997-2007
  ****************************************************************************
  * rrd_gfx.c  graphics wrapper for rrdtool
   **************************************************************************/
 
 /* #define DEBUG */
 
-#ifdef DEBUG
-# define DPRINT(x)    (void)(printf x, printf("\n"))
+/* stupid MSVC doesnt support variadic macros = no debug for now! */
+#ifdef _MSC_VER
+# define RRDPRINTF()
 #else
-# define DPRINT(x)
-#endif
+# ifdef DEBUG
+#  define RRDPRINTF(...)  fprintf(stderr, __VA_ARGS__);
+# else
+#  define RRDPRINTF(...)
+# endif                         /* DEBUG */
+#endif                          /* _MSC_VER */
 
-#include <png.h>
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include <math.h>
+#include "rrd_tool.h"
+#include "rrd_graph.h"
 
-#include "rrd_gfx.h"
-
-/* lines are better drawn on the pixle than between pixles */
-#define LINEOFFSET 0.5
-
-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);
-  if (node == NULL) return NULL;
-  node->type = type;
-  node->color = 0x0;        /* color of element  0xRRGGBBAA  alpha 0xff is solid*/
-  node->size =0.0;         /* font size, line width */
-  node->path = NULL;        /* path */
-  node->points = 0;
-  node->points_max =0;
-  node->svp = NULL;         /* svp */
-  node->filename = NULL;             /* font or image filename */
-  node->text = NULL;
-  node->x = 0.0;
-  node->y = 0.0;          /* position */
-  node->halign = GFX_H_NULL; /* text alignement */
-  node->valign = GFX_V_NULL; /* text alignement */
-  node->tabwidth = 0.0; 
-  node->next = NULL; 
-  if (canvas->lastnode != NULL){
-      canvas->lastnode->next = node;
-  }
-  if (canvas->firstnode == NULL){
-      canvas->firstnode = node;
-  }  
-  canvas->lastnode = node;
-  return node;
-}
-
-gfx_canvas_t *gfx_new_canvas (void) {
-    gfx_canvas_t *canvas = art_new(gfx_canvas_t,1);
-    canvas->firstnode = NULL;
-    canvas->lastnode = NULL;
-    return canvas;    
-}
 
 /* create a new line */
-gfx_node_t  *gfx_new_line(gfx_canvas_t *canvas, 
-                          double x0, double y0, 
-                          double x1, double y1,
-                          double width, gfx_color_t color){
-
-  gfx_node_t *node;
-  ArtVpath *vec;
-  node = gfx_new_node(canvas,GFX_LINE);
-  if (node == NULL) return NULL;
-  vec = art_new(ArtVpath, 3);
-  if (vec == NULL) return NULL;
-  vec[0].code = ART_MOVETO_OPEN; vec[0].x=x0+LINEOFFSET; vec[0].y=y0+LINEOFFSET;
-  vec[1].code = ART_LINETO; vec[1].x=x1+LINEOFFSET; vec[1].y=y1+LINEOFFSET;
-  vec[2].code = ART_END;
-  
-  node->points = 3;
-  node->points_max = 3;
-  node->color = color;
-  node->size  = width;
-  node->path  = vec;
-  return node;
+void gfx_line(
+    image_desc_t *im,
+    double X0,
+    double Y0,
+    double X1,
+    double Y1,
+    double width,
+    gfx_color_t color)
+{
+    gfx_dashed_line(im, X0, Y0, X1, Y1, width, color, 0, 0);
 }
 
-/* create a new area */
-gfx_node_t   *gfx_new_area   (gfx_canvas_t *canvas, 
-                             double x0, double y0,
-                             double x1, double y1,
-                             double x2, double y2,
-                             gfx_color_t color) {
-
-  gfx_node_t *node;
-  ArtVpath *vec;
-  node = gfx_new_node(canvas,GFX_AREA);
-  if (node == NULL) return NULL;
-  vec = art_new(ArtVpath, 5);
-  if (vec == NULL) return NULL;
-  vec[0].code = ART_MOVETO; vec[0].x=x0; vec[0].y=y0;
-  vec[1].code = ART_LINETO; vec[1].x=x1; vec[1].y=y1;
-  vec[2].code = ART_LINETO; vec[2].x=x2; vec[2].y=y2;
-  vec[3].code = ART_LINETO; vec[3].x=x0; vec[3].y=y0;
-  vec[4].code = ART_END;
-  
-  node->points = 5;
-  node->points_max = 5;
-  node->color = color;
-  node->path  = vec;
-
-  return node;
+void gfx_dashed_line(
+    image_desc_t *im,
+    double X0,
+    double Y0,
+    double X1,
+    double Y1,
+    double width,
+    gfx_color_t color,
+    double dash_on,
+    double dash_off)
+{
+    cairo_t  *cr = im->cr;
+    double    dashes[] = { dash_on, dash_off };
+    double    x = 0;
+    double    y = 0;
+
+    cairo_save(cr);
+    cairo_new_path(cr);
+    cairo_set_line_width(cr, width);
+    gfx_line_fit(im, &x, &y);
+    gfx_line_fit(im, &X0, &Y0);
+    cairo_move_to(cr, X0, Y0);
+    gfx_line_fit(im, &X1, &Y1);
+    cairo_line_to(cr, X1, Y1);
+    if (dash_on > 0 || dash_off > 0)
+        cairo_set_dash(cr, dashes, 2, x);
+    cairo_set_source_rgba(cr, color.red, color.green, color.blue,
+                          color.alpha);
+    cairo_stroke(cr);
+    cairo_restore(cr);
 }
-/* create an arc section (2*M_PI is full circle) */
-gfx_node_t   *gfx_arc_sect   (gfx_canvas_t *canvas, 
-                             double centerx, double centery,
-                             double radiusx, double radiusy,
-                             double start, double end,
-                             gfx_color_t color) {
-
-  gfx_node_t *node;
-  ArtVpath *vec;
-  int counter;
-  double position;
-
-/* 20 is too low, 100 is overkill */
-#define AMOUNT_OF_VECTORS 50
 
-  node = gfx_new_node(canvas,GFX_AREA);
-  if (node == NULL) return NULL;
-  vec = art_new(ArtVpath, AMOUNT_OF_VECTORS+4);
-  if (vec == NULL) return NULL;
-
-  vec[0].code = ART_MOVETO;
-  vec[0].x = centerx;
-  vec[0].y = centery;
-
-  for (counter=0;counter<=AMOUNT_OF_VECTORS;) {
-    position=start + counter*(end-start)/AMOUNT_OF_VECTORS;
-
-    counter++;
-    vec[counter].code = ART_LINETO;
-    vec[counter].x = centerx + sin(position)*radiusx;
-    vec[counter].y = centery - cos(position)*radiusy;
-  }
-
-  vec[AMOUNT_OF_VECTORS+2].code = ART_LINETO;
-  vec[AMOUNT_OF_VECTORS+2].x = centerx;
-  vec[AMOUNT_OF_VECTORS+2].y = centery;
-
-  vec[AMOUNT_OF_VECTORS+3].code = ART_END;
-  
-  node->points = AMOUNT_OF_VECTORS+4;
-  node->points_max = AMOUNT_OF_VECTORS+4;
-  node->color = color;
-  node->path  = vec;
-
-  return node;
+/* create a new area */
+void gfx_new_area(
+    image_desc_t *im,
+    double X0,
+    double Y0,
+    double X1,
+    double Y1,
+    double X2,
+    double Y2,
+    gfx_color_t color)
+{
+    cairo_t  *cr = im->cr;
+
+    cairo_new_path(cr);
+    gfx_area_fit(im, &X0, &Y0);
+    cairo_move_to(cr, X0, Y0);
+    gfx_area_fit(im, &X1, &Y1);
+    cairo_line_to(cr, X1, Y1);
+    gfx_area_fit(im, &X2, &Y2);
+    cairo_line_to(cr, X2, Y2);
+    cairo_set_source_rgba(cr, color.red, color.green, color.blue,
+                          color.alpha);
 }
 
 /* add a point to a line or to an area */
-int           gfx_add_point  (gfx_node_t *node, 
-                             double x, double y){
-  if (node == NULL) return 1;
-  if (node->type == GFX_AREA) {
-    double x0 = node->path[0].x;
-    double y0 = node->path[0].y;
-    node->points -= 2;
-    art_vpath_add_point (&(node->path),
-                         &(node->points),
-                         &(node->points_max),
-                         ART_LINETO,
-                         x,y);
-    art_vpath_add_point (&(node->path),
-                         &(node->points),
-                         &(node->points_max),
-                         ART_LINETO,
-                         x0,y0);
-    art_vpath_add_point (&(node->path),
-                         &(node->points),
-                         &(node->points_max),
-                         ART_END,
-                         0,0);
-  } else if (node->type == GFX_LINE) {
-    node->points -= 1;
-    art_vpath_add_point (&(node->path),
-                         &(node->points),
-                         &(node->points_max),
-                         ART_LINETO,
-                         x+LINEOFFSET,y+LINEOFFSET);
-    art_vpath_add_point (&(node->path),
-                         &(node->points),
-                         &(node->points_max),
-                         ART_END,
-                         0,0);
-    
-  } else {
-    /* can only add point to areas and lines */
-    return 1;
-  }
-  return 0;
+void gfx_add_point(
+    image_desc_t *im,
+    double x,
+    double y)
+{
+    cairo_t  *cr = im->cr;
+
+    gfx_area_fit(im, &x, &y);
+    cairo_line_to(cr, x, y);
 }
 
+void gfx_close_path(
+    image_desc_t *im)
+{
+    cairo_t  *cr = im->cr;
 
-
-/* create a text node */
-gfx_node_t   *gfx_new_text   (gfx_canvas_t *canvas,  
-                             double x, double y, gfx_color_t color,
-                             char* font, double size,                        
-                             double tabwidth, double angle,
-                             enum gfx_h_align_en h_align,
-                             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;
-   node->filename = strdup(font);
-   node->x = x;
-   node->y = y;
-   node->color = color;
-   node->tabwidth = tabwidth;
-   node->halign = h_align;
-   node->valign = v_align;
-   return node;
+    cairo_close_path(cr);
+    cairo_fill(cr);
 }
 
-double gfx_get_text_width ( double start, char* font, double size,                           
-                           double tabwidth, char* text){
-
-  FT_GlyphSlot  slot;
-  FT_UInt       previous=0;
-  FT_UInt       glyph_index=0;
-  FT_Bool       use_kerning;
-  int           error;
-  FT_Face       face;
-  FT_Library    library=NULL;  
-  double        text_width=0;
-  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 */       
+/* create a text node */
+static PangoLayout *gfx_prep_text(
+    image_desc_t *im,
+    double x,
+    gfx_color_t color,
+    char *font,
+    double size,
+    double tabwidth,
+    const char *text)
+{
+    PangoLayout *layout;
+    PangoFontDescription *font_desc;
+    cairo_t  *cr = im->cr;
+
+    /* for performance reasons we might
+       want todo that only once ... tabs will always
+       be the same */
+    long      i;
+    long      tab_count = strlen(text);
+    long      tab_shift = fmod(x, tabwidth);
+
+    PangoTabArray *tab_array;
+    PangoContext *pango_context;
+
+    tab_array = pango_tab_array_new(tab_count, (gboolean) (1));
+    for (i = 1; i <= tab_count; i++) {
+        pango_tab_array_set_tab(tab_array,
+                                i, PANGO_TAB_LEFT, tabwidth * i - tab_shift);
     }
-    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 */
-  FT_Done_FreeType(library);
-  return text_width;
+    cairo_new_path(cr);
+    cairo_set_source_rgba(cr, color.red, color.green, color.blue,
+                          color.alpha);
+    layout = pango_cairo_create_layout(cr);
+    pango_context = pango_layout_get_context(layout);
+    pango_cairo_context_set_font_options(pango_context, im->font_options);
+    pango_cairo_update_context(cr, pango_context);
+
+    pango_layout_set_tabs(layout, tab_array);
+    font_desc = pango_font_description_from_string(font);
+    pango_font_description_set_size(font_desc, size * PANGO_SCALE);
+    pango_layout_set_font_description(layout, font_desc);
+    pango_layout_set_markup(layout, text, -1);
+    return layout;
 }
-
-
-
-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, 
-                             double zoom, 
-                             gfx_color_t background, FILE *fp){
-    
-    
-    FT_Library    library;
-    gfx_node_t *node = canvas->firstnode;    
-    art_u8 red = background >> 24, green = (background >> 16) & 0xff;
-    art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
-    unsigned long pys_width = width * zoom;
-    unsigned long pys_height = height * zoom;
-    const int bytes_per_pixel = 3;
-    unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
-    art_u8 *buffer = art_new (art_u8, rowstride*pys_height);
-    art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
-    FT_Init_FreeType( &library );
-    while(node){
-        switch (node->type) {
-        case GFX_LINE:
-        case GFX_AREA: {   
-            ArtVpath *vec;
-            double dst[6];     
-            ArtSVP *svp;
-            art_affine_scale(dst,zoom,zoom);
-            vec = art_vpath_affine_transform(node->path,dst);
-            if(node->type == GFX_LINE){
-                svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
-                                             ART_PATH_STROKE_CAP_ROUND,
-                                             node->size*zoom,1,1);
-            } else {
-                svp = art_svp_from_vpath ( vec );
-            }
-            art_free(vec);
-            art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
-                               node->color, buffer, rowstride, NULL);
-            art_free(svp);
-            break;
-        }
-        case GFX_TEXT: {
-            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;
-
-            float pen_x = 0.0 , pen_y = 0.0;
-            /* double x,y; */
-            long   ix,iy,iz;
-            
-            fcolor[0] = node->color >> 24;
-            fcolor[1] = (node->color >> 16) & 0xff;
-            fcolor[2] = (node->color >> 8) & 0xff;
-            falpha = node->color & 0xff;
-            error = FT_New_Face( library,
-                                 (char *)node->filename,
-                                 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),
-                                     (long)(node->size*64),
-                                     (long)(100*zoom),
-                                     (long)(100*zoom));
-            if ( error ) break;
-            pen_x = node->x * zoom;
-            pen_y = node->y * 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 */
-            
-            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;          
-            }
-
-            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;          
-            }
 
-            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;
-                    
-                }
-                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;
-                    buf_y *= rowstride;
-                    for (ix=0;ix < slot->bitmap.width;ix++){
-                        long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
-                        art_u8 font_alpha;
-                        
-                        if (buf_x < 0 || buf_x >= pys_width) continue;
-                        buf_x *=  bytes_per_pixel ;
-                        font_alpha =  *(slot->bitmap.buffer + iy * slot->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;
-                            *orig =  (art_u8)((double)*orig / gr * ( gr - font_alpha) +
-                                              (double)fcolor[iz] / gr * (font_alpha));
-                        }
-                    }
-                }
-                pen_x += (double)slot->metrics.horiAdvance / 64.0;
-            }
-        }
-        }
-        node = node->next;
-    }  
-    gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
-    art_free(buffer);
-    FT_Done_FreeType( library );
-    return 0;    
-}
-
-/* free memory used by nodes this will also remove memory required for
-   associated paths and svcs ... but not for text strings */
-int
-gfx_destroy    (gfx_canvas_t *canvas){  
-  gfx_node_t *next,*node = canvas->firstnode;
-  while(node){
-    next = node->next;
-    art_free(node->path);
-    art_free(node->svp);
-    free(node->text);
-    free(node->filename);
-    art_free(node);
-    node = next;
-  }
-  return 0;
+/* Size Text Node */
+double gfx_get_text_width(
+    image_desc_t *im,
+    double start,
+    char *font,
+    double size,
+    double tabwidth,
+    char *text)
+{
+    PangoLayout *layout;
+    PangoRectangle log_rect;
+    gfx_color_t color = { 0, 0, 0, 0 };
+    char     *tab;
+
+    /* turn \\t into tab */
+    while ((tab = strstr(text, "\\t"))) {
+        memmove(tab + 1, tab + 2, strlen(tab + 2));
+        tab[0] = (char) 9;
+    }
+    layout = gfx_prep_text(im, start, color, font, size, tabwidth, text);
+    pango_layout_get_pixel_extents(layout, NULL, &log_rect);
+    pango_tab_array_free(pango_layout_get_tabs(layout));
+    g_object_unref(layout);
+    return log_rect.width;
 }
-static int gfx_save_png (art_u8 *buffer, FILE *fp,  long width, long height, long bytes_per_pixel){
-  png_structp png_ptr = NULL;
-  png_infop   info_ptr = NULL;
-  int i;
-  png_bytep *row_pointers;
-  int rowstride = width * bytes_per_pixel;
-  png_text text[2];
-  
-  if (fp == NULL)
-    return (1);
-
-  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
-  if (png_ptr == NULL)
-   {
-      return (1);
-   }
-   row_pointers = (png_bytepp)png_malloc(png_ptr,
-                                     height*sizeof(png_bytep));
-
-  info_ptr = png_create_info_struct(png_ptr);
 
-  if (info_ptr == NULL)
-    {
-      png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
-      return (1);
+void gfx_text(
+    image_desc_t *im,
+    double x,
+    double y,
+    gfx_color_t color,
+    char *font,
+    double size,
+    double tabwidth,
+    double angle,
+    enum gfx_h_align_en h_align,
+    enum gfx_v_align_en v_align,
+    const char *text)
+{
+    PangoLayout *layout;
+    PangoRectangle log_rect;
+    PangoRectangle ink_rect;
+    cairo_t  *cr = im->cr;
+    double    sx = 0;
+    double    sy = 0;
+
+    cairo_save(cr);
+    cairo_translate(cr, x, y);
+/*    gfx_line(cr,-2,0,2,0,1,color);
+    gfx_line(cr,0,-2,0,2,1,color); */
+    layout = gfx_prep_text(im, x, color, font, size, tabwidth, text);
+    pango_layout_get_pixel_extents(layout, &ink_rect, &log_rect);
+    cairo_rotate(cr, -angle * G_PI / 180.0);
+    sx = log_rect.x;
+    switch (h_align) {
+    case GFX_H_RIGHT:
+        sx -= log_rect.width;
+        break;
+    case GFX_H_CENTER:
+        sx -= log_rect.width / 2;
+        break;
+    case GFX_H_LEFT:
+        break;
+    case GFX_H_NULL:
+        break;
     }
-
-  if (setjmp(png_jmpbuf(png_ptr)))
-    {
-      /* If we get here, we had a problem writing the file */
-      png_destroy_write_struct(&png_ptr, &info_ptr);
-      return (1);
+    sy = log_rect.y;
+    switch (v_align) {
+    case GFX_V_TOP:
+        break;
+    case GFX_V_CENTER:
+        sy -= log_rect.height / 2;
+        break;
+    case GFX_V_BOTTOM:
+        sy -= log_rect.height;
+        break;
+    case GFX_V_NULL:
+        break;
     }
+    pango_cairo_update_layout(cr, layout);
+    cairo_move_to(cr, sx, sy);
+    pango_cairo_show_layout(cr, layout);
+    pango_tab_array_free(pango_layout_get_tabs(layout));
+    g_object_unref(layout);
+    cairo_restore(cr);
 
-  png_init_io(png_ptr, fp);
-  png_set_IHDR (png_ptr, info_ptr,width, height,
-                8, PNG_COLOR_TYPE_RGB,
-                PNG_INTERLACE_NONE,
-                PNG_COMPRESSION_TYPE_DEFAULT,
-                PNG_FILTER_TYPE_DEFAULT);
-
-  text[0].key = "Software";
-  text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
-  text[0].compression = PNG_TEXT_COMPRESSION_NONE;
-  png_set_text (png_ptr, info_ptr, text, 1);
+}
 
-  /* Write header data */
-  png_write_info (png_ptr, info_ptr);
+/* convert color */
+struct gfx_color_t gfx_hex_to_col(
+    long unsigned int color)
+{
+    struct gfx_color_t gfx_color;
+
+    gfx_color.red = 1.0 / 255.0 * ((color & 0xff000000) >> (3 * 8));
+    gfx_color.green = 1.0 / 255.0 * ((color & 0x00ff0000) >> (2 * 8));
+    gfx_color.blue = 1.0 / 255.0 * ((color & 0x0000ff00) >> (1 * 8));
+    gfx_color.alpha = 1.0 / 255.0 * (color & 0x000000ff);
+    return gfx_color;
+}
 
-  for (i = 0; i < height; i++)
-    row_pointers[i] = (png_bytep) (buffer + i*rowstride);
+/* gridfit_lines */
+
+void gfx_line_fit(
+    image_desc_t *im,
+    double *x,
+    double *y)
+{
+    cairo_t  *cr = im->cr;
+    double    line_width;
+    double    line_height;
+
+    if (!im->gridfit)
+        return;
+    cairo_user_to_device(cr, x, y);
+    line_width = cairo_get_line_width(cr);
+    line_height = line_width;
+    cairo_user_to_device_distance(cr, &line_width, &line_height);
+    line_width = line_width / 2.0 - (long) (line_width / 2.0);
+    line_height = line_height / 2.0 - (long) (line_height / 2.0);
+    *x = (double) ((long) (*x + 0.5)) - line_width;
+    *y = (double) ((long) (*y + 0.5)) + line_height;
+    cairo_device_to_user(cr, x, y);
+}
 
-  png_write_image(png_ptr, row_pointers);
-  png_write_end(png_ptr, info_ptr);
-  png_destroy_write_struct(&png_ptr, &info_ptr);
-  return 1;
+/* gridfit_areas */
+
+void gfx_area_fit(
+    image_desc_t *im,
+    double *x,
+    double *y)
+{
+    cairo_t  *cr = im->cr;
+
+    if (!im->gridfit)
+        return;
+    cairo_user_to_device(cr, x, y);
+    *x = (double) ((long) (*x + 0.5));
+    *y = (double) ((long) (*y + 0.5));
+    cairo_device_to_user(cr, x, y);
 }