/***************************************************************************

  cpaint_impl.cpp

  (c) 2000-2012 Benoît Minisini <gambas@users.sourceforge.net>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __CPAINT_IMPL_CPP

#include <cairo.h>
#include <cairo-xlib.h>

#include "gambas.h"
#include "gb_common.h"
#include "widgets.h"
#include "gdesktop.h"

#include "CWindow.h"
#include "CDrawingArea.h"
#include "CPicture.h"
#include "CImage.h"
#include "cprinter.h"
#include "csvgimage.h"
#include "CFont.h"
#include "CDraw.h"
#include "cpaint_impl.h"

/**** Cairo image management *********************************************/

static void free_image(GB_IMG *img, void *image)
{
	cairo_surface_destroy((cairo_surface_t *)image);
}

static void *temp_image(GB_IMG *img)
{
	cairo_surface_t *image;

	if (!img->data)
		image = NULL; // TODO: use a static small image surface
	else
		image = cairo_image_surface_create_for_data(img->data, CAIRO_FORMAT_ARGB32, img->width, img->height, 
		                                            cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, img->width));
	return image;
}

static GB_IMG_OWNER _image_owner = {
	"gb.gtk.cairo",
	GB_IMAGE_BGRP,
	free_image,
	free_image,
	temp_image
	};

static cairo_surface_t *check_image(void *img)
{
	// TODO: format is endian-dependent
	return (cairo_surface_t *)IMAGE.Check((GB_IMG *)img, &_image_owner);
}

/**** Paint implementation ***********************************************/

typedef
	struct {
		cairo_t *context;
		GtkPrintContext *print_context;
		CFONT *font;
		CFONT **font_stack;
		cairo_matrix_t init;
		}
	GB_PAINT_EXTRA;

#define EXTRA(d) ((GB_PAINT_EXTRA *)d->extra)
#define CONTEXT(d) EXTRA(d)->context

static bool init_painting(GB_PAINT *d, cairo_surface_t *target, double w, double h, int rx, int ry)
{
	GB_PAINT_EXTRA *dx = EXTRA(d);
	
	d->width = w;
	d->height = h;
	d->resolutionX = rx; //device->physicalDpiX();
	d->resolutionY = ry; //device->physicalDpiY();
	
	/*if (device->paintingActive())
	{
		GB.Error("Device already being painted");
		return TRUE;
	}*/
	
	if (target)
	{
		dx->context = cairo_create(target);
		cairo_surface_destroy(target);
	}
	
	dx->font = NULL;
	dx->font_stack = NULL;
	
	return FALSE;
}

#if 0
static void _gtk_print_context_rotate_according_to_orientation (GtkPrintContext *context, cairo_t *cr)
{
  cairo_matrix_t matrix;
  gdouble width, height;
	GtkPageSetup *page = gtk_print_context_get_page_setup(context);

  /*width = gtk_paper_size_get_width (paper_size, GTK_UNIT_INCH);
  width = width * context->surface_dpi_x / context->pixels_per_unit_x;
  height = gtk_paper_size_get_height (paper_size, GTK_UNIT_INCH);
  height = height * context->surface_dpi_y / context->pixels_per_unit_y;*/
	
	width = gtk_print_context_get_width(context);
	height = gtk_print_context_get_height(context);
  
  switch (gtk_page_setup_get_orientation (page))
    {
    default:
    case GTK_PAGE_ORIENTATION_PORTRAIT:
      break;
    case GTK_PAGE_ORIENTATION_LANDSCAPE:
			fprintf(stderr, "rotate landscape\n");
      cairo_translate (cr, 0, height);
      cairo_matrix_init (&matrix,
			 0, -1,
			 1,  0,
			 0,  0);
      cairo_transform (cr, &matrix);
      break;
    case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
      cairo_translate (cr, width, height);
      cairo_matrix_init (&matrix,
			 -1,  0,
			  0, -1,
			  0,  0);
      cairo_transform (cr, &matrix);
      break;
    case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
      cairo_translate (cr, width, 0);
      cairo_matrix_init (&matrix,
			  0,  1,
			 -1,  0,
			  0,  0);
      cairo_transform (cr, &matrix);
      break;
    }
}
#endif

static int Begin(GB_PAINT *d)
{
	void *device = d->device;
	cairo_surface_t *target = NULL;
	double w, h;
	int rx = 96, ry = 96;
	
	EXTRA(d)->print_context = NULL;
	
	if (GB.Is(device, CLASS_Picture))
	{
		gPicture *picture = ((CPICTURE *)device)->picture;
		GdkDrawable *pixmap;
		
		if (picture->isVoid())
		{
			GB.Error("Bad picture");
			return TRUE;
		}
		
		pixmap = (GdkDrawable *)picture->getPixmap();
		w = picture->width();
		h = picture->height();
		
		target = 
			cairo_xlib_surface_create(gdk_x11_drawable_get_xdisplay(pixmap), gdk_x11_drawable_get_xid(pixmap), 
				gdk_x11_visual_get_xvisual(gdk_drawable_get_visual(pixmap)), w, h);
	}
	else if (GB.Is(device, CLASS_Image))
	{
		target = check_image(device);
		cairo_surface_reference(target);
		w = ((GB_IMG *)device)->width;
		h = ((GB_IMG *)device)->height;
		if (!target)
		{
			GB.Error("Bad image");
			return TRUE;
		}
	}
	else if (GB.Is(device, CLASS_DrawingArea))
	{
		gDrawingArea *wid = (gDrawingArea *)((CWIDGET *)device)->widget;
		GdkDrawable *dr;
		
		w = wid->width();
		h = wid->height();

		if (wid->cached())
		{
			wid->resizeCache(); // Why is it needed?
			dr = wid->buffer;
		}
		else
		{
			if (!wid->inDrawEvent())
			{
				GB.Error("Cannot paint outside of Draw event handler");
				return TRUE;
			}
			dr = wid->widget->window;
		}

		/*target = 
			cairo_xlib_surface_create(gdk_x11_drawable_get_xdisplay(dr), gdk_x11_drawable_get_xid(dr), 
				gdk_x11_visual_get_xvisual(gdk_drawable_get_visual(dr)), w, h);*/
		
		d->resolutionX = gDesktop::resolution(); //device->physicalDpiX();
		d->resolutionY = gDesktop::resolution(); //device->physicalDpiY();
		
		EXTRA(d)->context = gdk_cairo_create(dr);
	}
	else if (GB.Is(device, CLASS_Printer))
	{
		GtkPrintContext *context = ((CPRINTER *)device)->context;
		
		if (!context)
		{
			GB.Error("Printer is not printing");
			return TRUE;
		}
		
		EXTRA(d)->print_context = context;
		EXTRA(d)->context = gtk_print_context_get_cairo_context(context);
		
		//_gtk_print_context_rotate_according_to_orientation(context, CONTEXT(d));
		
		/*fprintf(stderr, "Paint.Begin: orientation = %d w = %g h = %g\n", gtk_page_setup_get_orientation(gtk_print_context_get_page_setup(context)),
			gtk_page_setup_get_paper_width(gtk_print_context_get_page_setup(context), GTK_UNIT_MM),
			gtk_page_setup_get_paper_height(gtk_print_context_get_page_setup(context), GTK_UNIT_MM)
		);*/
		
		cairo_reference(CONTEXT(d));
		
		cairo_surface_set_fallback_resolution(cairo_get_target(CONTEXT(d)), 1200, 1200);
		
		w = gtk_print_context_get_width(context);
		h = gtk_print_context_get_height(context);
		//fprintf(stderr, "Paint.Begin: w = %g h = %g\n", w, h);
		
		rx = (int)gtk_print_context_get_dpi_x(context);
		ry = (int)gtk_print_context_get_dpi_y(context);
		
		cairo_get_matrix(CONTEXT(d), &EXTRA(d)->init);
		/*cairo_identity_matrix(CONTEXT(d));
		cairo_get_matrix(CONTEXT(d), &t);
		fprintf(stderr, "matrix: [%g %g %g]\n        [%g %g %g]\n", t.xx, t.xy, t.x0, t.yx, t.yy, t.y0);*/
	}
	else if (GB.Is(device, CLASS_SvgImage))
	{
		CSVGIMAGE *svgimage = ((CSVGIMAGE *)device);
		target = SVGIMAGE_begin(svgimage);
		if (!target)
			return TRUE;
		
		cairo_surface_reference(target);
		w = svgimage->width;
		h = svgimage->height;
		rx = ry = 72;
	}
	else
		return TRUE;
	
	return init_painting(d, target, w, h, rx, ry);
}

static void End(GB_PAINT *d)
{
	void *device = d->device;
	GB_PAINT_EXTRA *dx = EXTRA(d);

	if (dx->font_stack)
		GB.FreeArray(POINTER(&dx->font_stack));
	GB.Unref(POINTER(&dx->font));
	
	cairo_destroy(dx->context);

	if (GB.Is(device, CLASS_DrawingArea))
	{
		gDrawingArea *wid = (gDrawingArea *)((CWIDGET *)device)->widget;
		if (wid->cached())
			wid->setCache();
	}
	else if (GB.Is(device, CLASS_SvgImage))
	{
		CSVGIMAGE *svgimage = ((CSVGIMAGE *)device);
		SVGIMAGE_end(svgimage);
	}
}

static void Save(GB_PAINT *d)
{
	GB_PAINT_EXTRA *dx = EXTRA(d);
	CFONT **pfont;
	
	cairo_save(dx->context);
	
	if (!dx->font_stack)
		GB.NewArray(POINTER(&dx->font_stack), sizeof(void *), 0);
	
	pfont = (CFONT **)GB.Add(POINTER(&dx->font_stack));
	*pfont = dx->font;
	if (*pfont)
		GB.Ref(*pfont);
}

static void Restore(GB_PAINT *d)
{
	GB_PAINT_EXTRA *dx = EXTRA(d);
	
	cairo_restore(dx->context);
	
	if (dx->font_stack && GB.Count(dx->font_stack) > 0)
	{
		CFONT *font = dx->font_stack[GB.Count(dx->font_stack) - 1];
		GB.Unref(POINTER(&dx->font));
		dx->font = font;
		GB.Remove(POINTER(&dx->font_stack), GB.Count(dx->font_stack) - 1, 1);
	}
}

static void Antialias(GB_PAINT *d, int set, int *antialias)
{
	if (set)
		cairo_set_antialias(CONTEXT(d), *antialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
	else
		*antialias = (cairo_get_antialias(CONTEXT(d)) == CAIRO_ANTIALIAS_NONE) ? 0 : 1;
}

// Font is used by X11!
static void _Font(GB_PAINT *d, int set, GB_FONT *font)
{
	if (!EXTRA(d)->font)
	{
		EXTRA(d)->font = CFONT_create(new gFont());
		GB.Ref(EXTRA(d)->font);
	}
		
	if (set)
	{
		GB.Ref(*font);
		GB.Unref(POINTER(&EXTRA(d)->font));
		EXTRA(d)->font = (CFONT *)(*font);
	}
	else
		*font = (GB_FONT)EXTRA(d)->font;
}

/*static void init_path(GB_PAINT *d)
{
	switch (EXTRA(d)->fillRule)
	{
		case GB_PAINT_FILL_RULE_WINDING:
			PATH(d)->setFillRule(Qt::WindingFill); 
			break;
		case GB_PAINT_FILL_RULE_EVEN_ODD: 
		default:
			PATH(d)->setFillRule(Qt::OddEvenFill);
	}
}*/

static void Clip(GB_PAINT *d, int preserve)
{
	if (preserve)
		cairo_clip_preserve(CONTEXT(d));
	else
		cairo_clip(CONTEXT(d));
}

static void ResetClip(GB_PAINT *d)
{
	cairo_reset_clip(CONTEXT(d));
}

static void ClipExtents(GB_PAINT *d, GB_EXTENTS *ext)
{
	double x1, y1, x2, y2;
	cairo_clip_extents(CONTEXT(d), &x1, &y1, &x2, &y2);
	
	ext->x1 = (float)x1;
	ext->y1 = (float)y1;
	ext->x2 = (float)x2;
	ext->y2 = (float)y2;
}
	
static void Fill(GB_PAINT *d, int preserve)
{
	if (preserve)
		cairo_fill_preserve(CONTEXT(d));
	else
		cairo_fill(CONTEXT(d));
}

static void Stroke(GB_PAINT *d, int preserve)
{
	if (preserve)
		cairo_stroke_preserve(CONTEXT(d));
	else
		cairo_stroke(CONTEXT(d));
}
		
static void PathExtents(GB_PAINT *d, GB_EXTENTS *ext)
{
	double x1, y1, x2, y2;
	cairo_path_extents(CONTEXT(d), &x1, &y1, &x2, &y2);
	
	ext->x1 = (float)x1;
	ext->y1 = (float)y1;
	ext->x2 = (float)x2;
	ext->y2 = (float)y2;
}

static int PathContains(GB_PAINT *d, float x, float y)
{
	return cairo_in_fill(CONTEXT(d), (double)x, (double)y);
}

static void Dash(GB_PAINT *d, int set, float **dashes, int *count)
{
	int i;
	double lw;
	
	lw = cairo_get_line_width(CONTEXT(d));
	if (lw == 0) lw = 1;
	
	if (set)
	{
		if (!*dashes || *count == 0)
			cairo_set_dash(CONTEXT(d), NULL, 0, 0.0);
		else
		{
			double dd[*count];
			
			for (i = 0; i < *count; i++)
				dd[i] = (*dashes)[i] * lw;
			
			cairo_set_dash(CONTEXT(d), dd, *count, 0.0);
		}
	}
	else
	{
		*count = cairo_get_dash_count(CONTEXT(d));
		
		if (*count)
		{
			double dd[*count];
			cairo_get_dash(CONTEXT(d), dd, NULL);
			
			GB.Alloc(POINTER(dashes), sizeof(float) * *count);
			for (int i = 0; i < *count; i++)
				(*dashes)[i] = (float)dd[i] / lw;
		}
		else
		{
			*dashes = NULL;
		}
	}
}

static void DashOffset(GB_PAINT *d, int set, float *offset)
{
	double lw;
	
	lw = cairo_get_line_width(CONTEXT(d));
	if (lw == 0) lw = 1;

	if (set)
	{
		int count = cairo_get_dash_count(CONTEXT(d));
		double dashes[count];
		cairo_get_dash(CONTEXT(d), dashes, NULL);
		cairo_set_dash(CONTEXT(d), dashes, count, (double)*offset * lw);
	}
	else
	{
		double v;
		cairo_get_dash(CONTEXT(d), NULL, &v);
		*offset = (float)v / lw;
	}
}

		
static void FillRule(GB_PAINT *d, int set, int *value)
{
	if (set)
	{
		cairo_fill_rule_t v;
	
		switch(*value)
		{
			case GB_PAINT_FILL_RULE_EVEN_ODD: v = CAIRO_FILL_RULE_EVEN_ODD; break;
			case GB_PAINT_FILL_RULE_WINDING: default: v = CAIRO_FILL_RULE_WINDING;
		}
		
		cairo_set_fill_rule(CONTEXT(d), v);
	}
	else
	{
		switch(cairo_get_fill_rule(CONTEXT(d)))
		{
			case CAIRO_FILL_RULE_EVEN_ODD: *value = GB_PAINT_FILL_RULE_EVEN_ODD; break;
			case CAIRO_FILL_RULE_WINDING: default: *value = GB_PAINT_FILL_RULE_WINDING;
		}
	}
}

static void LineCap(GB_PAINT *d, int set, int *value)
{
	if (set)
	{
		cairo_line_cap_t v;
		
		switch (*value)
		{
			case GB_PAINT_LINE_CAP_ROUND: v = CAIRO_LINE_CAP_ROUND; break;
			case GB_PAINT_LINE_CAP_SQUARE: v = CAIRO_LINE_CAP_SQUARE; break;
			case GB_PAINT_LINE_CAP_BUTT: default: v = CAIRO_LINE_CAP_BUTT;
		}
		
		cairo_set_line_cap(CONTEXT(d), v);
	}
	else
	{
		switch (cairo_get_line_cap(CONTEXT(d)))
		{
			case CAIRO_LINE_CAP_ROUND: *value = GB_PAINT_LINE_CAP_ROUND; break;
			case CAIRO_LINE_CAP_SQUARE: *value = GB_PAINT_LINE_CAP_SQUARE; break;
			case CAIRO_LINE_CAP_BUTT: default: *value = GB_PAINT_LINE_CAP_BUTT;
		}
	}
}

static void LineJoin(GB_PAINT *d, int set, int *value)
{
	if (set)
	{
		cairo_line_join_t v;
		
		switch (*value)
		{
			case GB_PAINT_LINE_JOIN_ROUND: v = CAIRO_LINE_JOIN_ROUND; break;
			case GB_PAINT_LINE_JOIN_BEVEL: v = CAIRO_LINE_JOIN_BEVEL; break;
			case GB_PAINT_LINE_JOIN_MITER: default: v = CAIRO_LINE_JOIN_MITER;
		}
		
		cairo_set_line_join(CONTEXT(d), v);
	}
	else
	{
		switch (cairo_get_line_join(CONTEXT(d)))
		{
			case CAIRO_LINE_JOIN_ROUND: *value = GB_PAINT_LINE_JOIN_ROUND; break;
			case CAIRO_LINE_JOIN_BEVEL: *value = GB_PAINT_LINE_JOIN_BEVEL; break;
			case CAIRO_LINE_JOIN_MITER: default: *value = GB_PAINT_LINE_JOIN_MITER;
		}
	}
}

static void LineWidth(GB_PAINT *d, int set, float *value)
{
	if (set)
	{
		float *dashes;
		int count;
		float offset;
		
		Dash(d, FALSE, &dashes, &count);
		DashOffset(d, FALSE, &offset);
		
		cairo_set_line_width(CONTEXT(d), (double)*value);
		
		Dash(d, TRUE, &dashes, &count);
		DashOffset(d, TRUE, &offset);
		GB.Free(POINTER(&dashes));
	}
	else
		*value = (float)cairo_get_line_width(CONTEXT(d));
}

static void MiterLimit(GB_PAINT *d, int set, float *value)
{
	if (set)
		cairo_set_miter_limit(CONTEXT(d), (double)*value);
	else
		*value = (float)cairo_get_miter_limit(CONTEXT(d));
}


static void Operator(GB_PAINT *d, int set, int *value)
{
	if (set)
	{
		cairo_operator_t v;
		
		switch (*value)
		{
			case GB_PAINT_OPERATOR_CLEAR: v = CAIRO_OPERATOR_CLEAR; break;
			case GB_PAINT_OPERATOR_SOURCE: v = CAIRO_OPERATOR_SOURCE; break;
			case GB_PAINT_OPERATOR_IN: v = CAIRO_OPERATOR_IN; break;
			case GB_PAINT_OPERATOR_OUT: v = CAIRO_OPERATOR_OUT; break;
			case GB_PAINT_OPERATOR_ATOP: v = CAIRO_OPERATOR_ATOP; break;
			case GB_PAINT_OPERATOR_DEST: v = CAIRO_OPERATOR_DEST; break;
			case GB_PAINT_OPERATOR_DEST_OVER: v = CAIRO_OPERATOR_DEST_OVER; break;
			case GB_PAINT_OPERATOR_DEST_IN: v = CAIRO_OPERATOR_DEST_IN; break;
			case GB_PAINT_OPERATOR_DEST_OUT: v = CAIRO_OPERATOR_DEST_OUT; break;
			case GB_PAINT_OPERATOR_DEST_ATOP: v = CAIRO_OPERATOR_DEST_ATOP; break;
			case GB_PAINT_OPERATOR_XOR: v = CAIRO_OPERATOR_XOR; break;
			case GB_PAINT_OPERATOR_ADD: v = CAIRO_OPERATOR_ADD; break;
			case GB_PAINT_OPERATOR_SATURATE: v = CAIRO_OPERATOR_SATURATE; break;
			case GB_PAINT_OPERATOR_OVER: default: v = CAIRO_OPERATOR_OVER; break;
		}
		
		cairo_set_operator(CONTEXT(d), v);
	}
	else
	{
		switch (cairo_get_operator(CONTEXT(d)))
		{
			case CAIRO_OPERATOR_CLEAR: *value = GB_PAINT_OPERATOR_CLEAR; break;
			case CAIRO_OPERATOR_SOURCE: *value = GB_PAINT_OPERATOR_SOURCE; break;
			case CAIRO_OPERATOR_IN: *value = GB_PAINT_OPERATOR_IN; break;
			case CAIRO_OPERATOR_OUT: *value = GB_PAINT_OPERATOR_OUT; break;
			case CAIRO_OPERATOR_ATOP: *value = GB_PAINT_OPERATOR_ATOP; break;
			case CAIRO_OPERATOR_DEST: *value = GB_PAINT_OPERATOR_DEST; break;
			case CAIRO_OPERATOR_DEST_OVER: *value = GB_PAINT_OPERATOR_DEST_OVER; break;
			case CAIRO_OPERATOR_DEST_IN: *value = GB_PAINT_OPERATOR_DEST_IN; break;
			case CAIRO_OPERATOR_DEST_OUT: *value = GB_PAINT_OPERATOR_DEST_OUT; break;
			case CAIRO_OPERATOR_DEST_ATOP: *value = GB_PAINT_OPERATOR_DEST_ATOP; break;
			case CAIRO_OPERATOR_XOR: *value = GB_PAINT_OPERATOR_XOR; break;
			case CAIRO_OPERATOR_ADD: *value = GB_PAINT_OPERATOR_ADD; break;
			case CAIRO_OPERATOR_SATURATE: *value = GB_PAINT_OPERATOR_SATURATE; break;
			case CAIRO_OPERATOR_OVER: default: *value = GB_PAINT_OPERATOR_OVER;
		}
	}
}


static void NewPath(GB_PAINT *d)
{
	cairo_new_path(CONTEXT(d));
}

static void ClosePath(GB_PAINT *d)
{
	cairo_close_path(CONTEXT(d));
}

		
static void Arc(GB_PAINT *d, float xc, float yc, float radius, float angle, float length)
{
	cairo_new_sub_path(CONTEXT(d));
	//angle = - angle;
	if (length < 0.0)
		cairo_arc_negative(CONTEXT(d), xc, yc, radius, angle, angle + length);
	else
		cairo_arc(CONTEXT(d), xc, yc, radius, angle, angle + length);
}

static void Rectangle(GB_PAINT *d, float x, float y, float width, float height)
{
	cairo_rectangle(CONTEXT(d), x, y, width, height);
}

static void GetCurrentPoint(GB_PAINT *d, float *x, float *y)
{
	double cx, cy;
	
	cairo_get_current_point(CONTEXT(d), &cx, &cy);
	*x = (float)cx;
	*y = (float)cy;
}

static void MoveTo(GB_PAINT *d, float x, float y)
{
	cairo_move_to(CONTEXT(d), x, y);
}

static void LineTo(GB_PAINT *d, float x, float y)
{
	cairo_line_to(CONTEXT(d), x, y);
}

static void CurveTo(GB_PAINT *d, float x1, float y1, float x2, float y2, float x3, float y3)
{
	cairo_curve_to(CONTEXT(d), x1, y1, x2, y2, x3, y3);
}

static PangoLayout *create_pango_layout(GB_PAINT *d)
{
	GB_PAINT_EXTRA *dx = EXTRA(d);
	
	/*if (dx->print_context)
		return gtk_print_context_create_pango_layout(dx->print_context);
	else*/
		return pango_cairo_create_layout(dx->context);
}

static void draw_text(GB_PAINT *d, bool rich, const char *text, int len, float w, float h, int align, bool draw)
{
	char *html = NULL;
  PangoLayout *layout;
	CFONT *font;
	float tw, th, offx, offy;

	layout = create_pango_layout(d);
  
	if (rich)
	{
		html = gt_html_to_pango_string(text, len, false);
		pango_layout_set_markup(layout, html, -1);
		if (w > 0)
			pango_layout_set_width(layout, (int)(w * PANGO_SCALE));
	}
	else
		pango_layout_set_text(layout, text, len);
	
	_Font(d, FALSE, (GB_FONT *)&font);
	gt_add_layout_from_font(layout, font->font, d->resolutionY);
	
	if (align == GB_DRAW_ALIGN_DEFAULT)
		align = ALIGN_TOP_NORMAL;
	
	if (w > 0 && h > 0)
	{
		gt_layout_alignment(layout, w, h, &tw, &th, align, &offx, &offy);
		if (rich)
			offx = 0;
	}
	else
	{
		offx = 0;
		offy = -(font->font->ascentF());
	}
	
	cairo_rel_move_to(CONTEXT(d), offx, offy);
	if (draw)
		pango_cairo_show_layout(CONTEXT(d), layout);
	else
		pango_cairo_layout_path(CONTEXT(d), layout);

	g_object_unref(layout);	
	
	if (html) g_free(html);
}

static void Text(GB_PAINT *d, const char *text, int len, float w, float h, int align, bool draw)
{
	draw_text(d, FALSE, text, len, w, h, align, draw);
}

static void RichText(GB_PAINT *d, const char *text, int len, float w, float h, int align, bool draw)
{
	draw_text(d, TRUE, text, len, w, h, align, draw);
}


static void get_text_extents(GB_PAINT *d, bool rich, const char *text, int len, GB_EXTENTS *ext, float width)
{
	char *html = NULL;
	PangoLayout *layout;
	CFONT *font;
	PangoRectangle rect;
	float x, y;
	
	layout = create_pango_layout(d);
	
	if (rich)
	{
		html = gt_html_to_pango_string(text, len, false);
		pango_layout_set_markup(layout, html, -1);
	}
	else
		pango_layout_set_text(layout, text, len);

	_Font(d, FALSE, (GB_FONT *)&font);
	gt_add_layout_from_font(layout, font->font, d->resolutionY);

	if (width > 0)
		pango_layout_set_width(layout, width * PANGO_SCALE);

	pango_layout_get_extents(layout, &rect, NULL);
	
	GetCurrentPoint(d, &x, &y);
	
	ext->x1 = (float)rect.x / PANGO_SCALE + x;
	ext->y1 = (float)rect.y / PANGO_SCALE + y - font->font->ascentF();
	ext->x2 = ext->x1 + (float)rect.width / PANGO_SCALE;
	ext->y2 = ext->y1 + (float)rect.height / PANGO_SCALE;
	
	g_object_unref(layout);
	if (html) g_free(html);
}

static void TextExtents(GB_PAINT *d, const char *text, int len, GB_EXTENTS *ext)
{
	get_text_extents(d, FALSE, text, len, ext, -1);
}

static void RichTextExtents(GB_PAINT *d, const char *text, int len, GB_EXTENTS *ext, float width)
{
	get_text_extents(d, TRUE, text, len, ext, width);
}

static void Matrix(GB_PAINT *d, int set, GB_TRANSFORM matrix)
{
	cairo_matrix_t *t = (cairo_matrix_t *)matrix;
	
	if (set)
	{
		if (t)
			cairo_set_matrix(CONTEXT(d), t);
		else
		{
			if (EXTRA(d)->print_context)
				cairo_set_matrix(CONTEXT(d), &EXTRA(d)->init);
			else
				cairo_identity_matrix(CONTEXT(d));
		}
	}
	else
		cairo_get_matrix(CONTEXT(d), t);
}

		
static void SetBrush(GB_PAINT *d, GB_BRUSH brush)
{
	cairo_set_source(CONTEXT(d), (cairo_pattern_t *)brush);
}


static void BrushFree(GB_BRUSH brush)
{
	// Should I release the surface associated with an image brush? Apparently no.
	cairo_pattern_destroy((cairo_pattern_t *)brush);
}

static void BrushColor(GB_BRUSH *brush, GB_COLOR color)
{
	int r, g, b, a;
	GB_COLOR_SPLIT(color, r, g, b, a);
	*brush = (GB_BRUSH)cairo_pattern_create_rgba(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
}

// Function partially taken from the GTK+ source code.

static cairo_surface_t *gdk_cairo_create_surface_from_pixbuf(const GdkPixbuf *pixbuf)
{
  gint width = gdk_pixbuf_get_width (pixbuf);
  gint height = gdk_pixbuf_get_height (pixbuf);
  guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
  int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
  int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
  int cairo_stride;
  guchar *cairo_pixels;
  cairo_format_t format;
  cairo_surface_t *surface;
  static const cairo_user_data_key_t key = { 0 };
  int j;

  if (n_channels == 3)
    format = CAIRO_FORMAT_RGB24;
  else
    format = CAIRO_FORMAT_ARGB32;

  cairo_stride = cairo_format_stride_for_width (format, width);
  cairo_pixels = (uchar *)g_malloc (height * cairo_stride);
  surface = cairo_image_surface_create_for_data ((unsigned char *)cairo_pixels,
                                                 format,
                                                 width, height, cairo_stride);

  cairo_surface_set_user_data (surface, &key,
			       cairo_pixels, (cairo_destroy_func_t)g_free);

  for (j = height; j; j--)
    {
      guchar *p = gdk_pixels;
      guchar *q = cairo_pixels;

      if (n_channels == 3)
	{
	  guchar *end = p + 3 * width;
	  
	  while (p < end)
	    {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	      q[0] = p[2];
	      q[1] = p[1];
	      q[2] = p[0];
#else	  
	      q[1] = p[0];
	      q[2] = p[1];
	      q[3] = p[2];
#endif
	      p += 3;
	      q += 4;
	    }
	}
      else
	{
	  guchar *end = p + 4 * width;
	  guint t1,t2,t3;
	    
#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END

	  while (p < end)
	    {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	      MULT(q[0], p[2], p[3], t1);
	      MULT(q[1], p[1], p[3], t2);
	      MULT(q[2], p[0], p[3], t3);
	      q[3] = p[3];
#else	  
	      q[0] = p[3];
	      MULT(q[1], p[0], p[3], t1);
	      MULT(q[2], p[1], p[3], t2);
	      MULT(q[3], p[2], p[3], t3);
#endif
	      
	      p += 4;
	      q += 4;
	    }
	  
#undef MULT
	}

      gdk_pixels += gdk_rowstride;
      cairo_pixels += cairo_stride;
    }

	return surface;
}

static void BrushImage(GB_BRUSH *brush, GB_IMAGE image)
{
	gPicture *picture = CIMAGE_get((CIMAGE *)image);
	cairo_surface_t *surface;
	cairo_pattern_t *pattern;

	surface = gdk_cairo_create_surface_from_pixbuf(picture->getPixbuf());
	
	pattern = cairo_pattern_create_for_surface(surface);
	cairo_surface_destroy(surface);

	cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
	
	*brush = (GB_BRUSH)pattern;
}

static void handle_color_stop(cairo_pattern_t *pattern, int nstop, const double *positions, const GB_COLOR *colors)
{
	int i, r, g, b, a;
	
	for (i = 0; i < nstop; i++)
	{
		GB_COLOR_SPLIT(colors[i], r, g, b, a);
		cairo_pattern_add_color_stop_rgba(pattern, positions[i], r / 255.0, g / 255.0, b / 255.0, a / 255.0);
	}
}

static void set_pattern_extend(cairo_pattern_t *pattern, int extend)
{
	cairo_extend_t cext;

	switch (extend)
	{
		case GB_PAINT_EXTEND_REPEAT: cext = CAIRO_EXTEND_REPEAT; break;
		case GB_PAINT_EXTEND_REFLECT: cext = CAIRO_EXTEND_REFLECT; break;
		case GB_PAINT_EXTEND_PAD: default: cext = CAIRO_EXTEND_PAD;
	}
	
	cairo_pattern_set_extend(pattern, cext);
}

static void BrushLinearGradient(GB_BRUSH *brush, float x0, float y0, float x1, float y1, int nstop, double *positions, GB_COLOR *colors, int extend)
{
	cairo_pattern_t *pattern;
		
	pattern = cairo_pattern_create_linear(x0, y0, x1, y1);
	
	handle_color_stop(pattern, nstop, positions, colors);
	set_pattern_extend(pattern, extend);
	
	*brush = (GB_BRUSH)pattern;
}

static void BrushRadialGradient(GB_BRUSH *brush, float cx, float cy, float r, float fx, float fy, int nstop, double *positions, GB_COLOR *colors, int extend)
{
	cairo_pattern_t *pattern;
		
	// I know that from librsvg sources
	pattern = cairo_pattern_create_radial(fx, fy, 0.0, cx, cy, r);

	handle_color_stop(pattern, nstop, positions, colors);
	set_pattern_extend(pattern, extend);

	*brush = (GB_BRUSH)pattern;
}

// Matrix must be inverted, so that it behaves the same way as in Qt4

static void BrushMatrix(GB_BRUSH brush, int set, GB_TRANSFORM matrix)
{
	cairo_matrix_t *t = (cairo_matrix_t *)matrix;
	cairo_pattern_t *pattern = (cairo_pattern_t *)brush;
	cairo_matrix_t actual;
	
	if (set)
	{
		if (t)
		{
			actual = *t;
			cairo_matrix_invert(&actual);
		}
		else
			cairo_matrix_init_identity(&actual);
		
		cairo_pattern_set_matrix(pattern, &actual);
	}
	else
	{
		cairo_pattern_get_matrix(pattern, t);
		cairo_matrix_invert(t);
	}
}

static void TransformCreate(GB_TRANSFORM *matrix)
{
	GB.Alloc(POINTER(matrix), sizeof(cairo_matrix_t));
	cairo_matrix_init_identity((cairo_matrix_t *)*matrix);
}

static void TransformDelete(GB_TRANSFORM *matrix)
{
	GB.Free(POINTER(matrix));
}

static void TransformInit(GB_TRANSFORM matrix, float xx, float yx, float xy, float yy, float x0, float y0)
{
	cairo_matrix_init((cairo_matrix_t *)matrix, xx, yx, xy, yy, x0, y0);
}

static void TransformTranslate(GB_TRANSFORM matrix, float tx, float ty)
{
	cairo_matrix_translate((cairo_matrix_t *)matrix, tx, ty);
}

static void TransformScale(GB_TRANSFORM matrix, float sx, float sy)
{
	cairo_matrix_scale((cairo_matrix_t *)matrix, sx, sy);
}

static void TransformRotate(GB_TRANSFORM matrix, float angle)
{
	cairo_matrix_rotate((cairo_matrix_t *)matrix, -angle);
}

static int TransformInvert(GB_TRANSFORM matrix)
{
	cairo_status_t status = cairo_matrix_invert((cairo_matrix_t *)matrix);
	return status != CAIRO_STATUS_SUCCESS;
}

static void TransformMultiply(GB_TRANSFORM matrix, GB_TRANSFORM matrix2)
{
	cairo_matrix_multiply((cairo_matrix_t *)matrix, (cairo_matrix_t *)matrix, (cairo_matrix_t *)matrix2);
}

static void DrawImage(GB_PAINT *d, GB_IMAGE image, float x, float y, float w, float h)
{
	cairo_pattern_t *pattern, *save;
	cairo_matrix_t matrix;
	gPicture *picture = CIMAGE_get((CIMAGE *)image);
	
	cairo_save(CONTEXT(d));
	
	save = cairo_get_source(CONTEXT(d));
	cairo_pattern_reference(save);
	
	BrushImage((GB_BRUSH *)&pattern, image);
	
	//gdk_cairo_set_source_pixbuf(CONTEXT(d), picture->getPixbuf(), x, y);
	
	cairo_matrix_init_identity(&matrix);
	cairo_matrix_translate(&matrix, x, y);
	cairo_matrix_scale(&matrix, w / picture->width(), h / picture->height());
	cairo_matrix_invert(&matrix);
	cairo_pattern_set_matrix(pattern, &matrix);
	cairo_set_source(CONTEXT(d), pattern);
	
	cairo_rectangle(CONTEXT(d), x, y, w, h);
	cairo_fill(CONTEXT(d));
	
	cairo_set_source(CONTEXT(d), save);
	cairo_pattern_destroy(save);
	
	cairo_restore(CONTEXT(d));
}


GB_PAINT_DESC PAINT_Interface = 
{
	sizeof(GB_PAINT_EXTRA),
	Begin,
	End,
	Save,
	Restore,
	Antialias,
	_Font,
	Clip,
	ResetClip,
	ClipExtents,
	Fill,
	Stroke,
	PathExtents,
	PathContains,
	Dash,
	DashOffset,
	FillRule,
	LineCap,
	LineJoin,
	LineWidth,
	MiterLimit,
	Operator,
	NewPath,
	ClosePath,
	Arc,
	Rectangle,
	GetCurrentPoint,
	MoveTo,
	LineTo,
	CurveTo,
	Text,
	TextExtents,
	RichText,
	RichTextExtents,
	Matrix,
	SetBrush,
	DrawImage,
	{
		BrushFree,
		BrushColor,
		BrushImage,
		BrushLinearGradient,
		BrushRadialGradient,
		BrushMatrix,
	},
	{
		TransformCreate,
		TransformDelete,
		TransformInit,
		TransformTranslate,
		TransformScale,
		TransformRotate,
		TransformInvert,
		TransformMultiply
	}
};

void PAINT_begin(void *device)
{
	DRAW.Paint.Begin(device);
}

void PAINT_end()
{
	DRAW.Paint.End();
}

void PAINT_clip(int x, int y, int w, int h)
{
	GB_PAINT *d = (GB_PAINT *)DRAW.Paint.GetCurrent();
	if (d)
	{
		cairo_rectangle(CONTEXT(d), (double)x, (double)y, (double)w, (double)h);
		cairo_clip(CONTEXT(d));
	}
}

cairo_t *PAINT_get_current_context()
{
	GB_PAINT *d = (GB_PAINT *)DRAW.Paint.GetCurrent();
	if (d)
		return CONTEXT(d);
	
	GB.Error("No current device");
	return NULL;
}

