/* dia-shape-art.c
 * Copyright (C) 2001  Arjan Molenaar
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "dia-shape-art.h"
#include <math.h>
#include <stdlib.h>
#include <pango/pangoft2.h>
#include <libgnomecanvas/gnome-canvas-util.h>
#include <libart_lgpl/art_rect.h>
#include <libart_lgpl/art_rgb_affine.h>
#include <libart_lgpl/art_rgb_a_affine.h>
#include <libart_lgpl/art_rgb_rgba_affine.h>
#include <libart_lgpl/art_svp.h>
#include <libart_lgpl/art_svp_vpath_stroke.h>
#include <libart_lgpl/art_svp_ops.h>
#include <libart_lgpl/art_svp_vpath.h>
#include <libart_lgpl/art_svp_wind.h>
#include <libart_lgpl/art_uta_rect.h>
#include <libart_lgpl/art_uta_svp.h>
#include <libart_lgpl/art_uta_ops.h>
#include <libart_lgpl/art_vpath_bpath.h>
#include <libart_lgpl/art_misc.h>

static inline ArtPathStrokeJoinType
dia_join_style_to_art (DiaJoinStyle js)
{
	switch (js) {
	case DIA_JOIN_MITER:
		return ART_PATH_STROKE_JOIN_MITER;
	case DIA_JOIN_ROUND:
		return ART_PATH_STROKE_JOIN_ROUND;
	case DIA_JOIN_BEVEL:
		return ART_PATH_STROKE_JOIN_BEVEL;
	default:
		g_warning ("Invalid DiaJoinStyle type: %d.", js);
		return ART_PATH_STROKE_JOIN_MITER;
	}
}

static inline ArtPathStrokeCapType
dia_cap_style_to_art (DiaCapStyle cs)
{
	switch (cs) {
	case DIA_CAP_BUTT:
		return ART_PATH_STROKE_CAP_BUTT;
	case DIA_CAP_ROUND:
		return ART_PATH_STROKE_CAP_ROUND;
	case DIA_CAP_SQUARE:
		return ART_PATH_STROKE_CAP_SQUARE;
	default:
		g_warning ("Invalid DiaCapStyle type: %d.", cs);
		return ART_PATH_STROKE_CAP_BUTT;
	}
}

/**
 * dia_shape_art_get_pango_layout:
 *
 * Return a new PangoLayout belonging to the AA renderer.
 *
 * Return value: A newly created PangoLayout object.
 **/
PangoLayout*
dia_shape_art_get_pango_layout (void)
{
	/* Use the default (canvas) PangoLayout setup. */
	return dia_canvas_get_pango_layout ();
}

static ArtSVP*
svp_dup (ArtSVP* svp)
{
	register int sz = sizeof(ArtSVP) + (svp->n_segs - 1) * sizeof (ArtSVPSeg);
	int i;
	ArtSVP *new_svp;

	new_svp = art_alloc (sz);
	memcpy (new_svp, svp, sz);

	/* copy ArtSVPSeg.points */
	for (i = 0; i < new_svp->n_segs; i++) {
		sz = new_svp->segs[i].n_points * sizeof (ArtPoint);
		new_svp->segs[i].points = art_alloc (sz);
		memcpy (new_svp->segs[i].points, svp->segs[i].points, sz);
	}
	return new_svp;
}

/*
 * Path
 */
static void path_free (DiaShapeViewInfo *view_info);

#define PERTURBATION 2e-3
#define ELLIPSE 1e-3
#define RANDOM ((PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5);
/* Do not render if the size is smaller than a quarter point. */
#define MIN_LINE_WIDTH 0.1
#define MIN_SIZE 0.25

static ArtUta*
create_svps_from_vpath (ArtVpath *vpath, gdouble line_width,
			DiaJoinStyle join, DiaCapStyle cap,
			gboolean cyclic, ArtVpathDash *dash,
			gdouble *affine, ArtSVP *clip,
			ArtSVP **stroke_svp, ArtSVP **fill_svp)
{
	ArtVpath *path = NULL;
	guint i, seg = 1;
	//gdouble min = 10.0;

	if (line_width > 1000 || !(stroke_svp || fill_svp))
		return NULL;

	if (!vpath) {
		if (stroke_svp)
			*stroke_svp = NULL;
		if (fill_svp)
			*fill_svp = NULL;
		return NULL;
	}
	/* Determine how much memory should be allocated for a
	 * copy of the shape's vpath. */
	for (path = vpath; path->code != ART_END; path++)
		seg++;

	if (cyclic)
		seg++;

	path = g_new (ArtVpath, seg);

	if (affine
	    && (fabs (affine[0] - 1.0) < ELLIPSE)
	    && (fabs (affine[1]) < ELLIPSE)
	    && (fabs (affine[2]) < ELLIPSE)
	    && (fabs (affine[3] -1.0) < ELLIPSE)) {
		for (i = 0; vpath[i].code != ART_END; i++) {
			path[i].code = vpath[i].code;
			path[i].x = vpath[i].x + affine[4] + RANDOM;
			//min = MIN(min, path[i].x);
			path[i].y = vpath[i].y + affine[5] + RANDOM;
			//min = MIN(min, path[i].y);
		}
	} else if (affine) {
		line_width = line_width * art_affine_expansion (affine);

		for (i = 0; vpath[i].code != ART_END; i++) {
			path[i].code = vpath[i].code;
			path[i].x = affine[0] * vpath[i].x
			       + affine[2] * vpath[i].y + affine[4] + RANDOM;
			//min = MIN(min, path[i].x);
			path[i].y = affine[1] * vpath[i].x
			       + affine[3] * vpath[i].y + affine[5] + RANDOM;
			//min = MIN(min, path[i].y);
		}
	} else {
		for (i = 0; vpath[i].code != ART_END; i++) {
			path[i].code = vpath[i].code;
			path[i].x = vpath[i].x + RANDOM;
			//min = MIN(min, path[i].x);
			path[i].y = vpath[i].y + RANDOM;
			//min = MIN(min, path[i].y);
		}
	}

	//if (min < MIN_SIZE)
		//return NULL;

	/* Add an extra segment to close the line */
	if (cyclic) {
		path[i].code = ART_LINETO;
		path[i].x = path[0].x;
		path[i].y = path[0].y;
		i++;
	}
	path[i].code = ART_END;
	path[i].x = path[i].y = 0.0;
	g_assert (i == seg - 1);

	if (stroke_svp) {
		ArtSVP *svp, *tmp_svp;
		ArtVpath *tmp_path;

		if (dash)
			tmp_path = art_vpath_dash (path, dash);
		else
			tmp_path = path;

		svp = art_svp_vpath_stroke (tmp_path,
					    dia_join_style_to_art (join),
					    dia_cap_style_to_art (cap),
					    line_width,
					    4,
					    0.25);
		if (tmp_path != path)
			art_free (tmp_path);
		tmp_svp = art_svp_uncross (svp);
		art_svp_free (svp);
		svp = art_svp_rewind_uncrossed (tmp_svp, ART_WIND_RULE_NONZERO);
		art_svp_free (tmp_svp);

		if (clip) {
			tmp_svp = svp;
			svp = art_svp_intersect (tmp_svp, clip);
			art_svp_free (tmp_svp);
		}
		*stroke_svp = svp;
	}

	/* Create the fill SVP */
	if (fill_svp) {
		ArtVpath *tmp_path;
		ArtSVP *svp, *tmp_svp;

		tmp_path = art_vpath_perturb (path);
		svp = art_svp_from_vpath (tmp_path);
		art_free (tmp_path);
		tmp_svp = art_svp_uncross (svp);
		art_svp_free (svp);
		svp = art_svp_rewind_uncrossed (tmp_svp, ART_WIND_RULE_ODDEVEN);
		art_svp_free (tmp_svp);

		if (clip) {
			tmp_svp = svp;
			svp = art_svp_intersect (tmp_svp, clip);
			art_svp_free (tmp_svp);
		}
		*fill_svp = svp;
	}

	g_free (path);

	/* The srtoke has a line wirdth, so it is probably the */

	if (stroke_svp && fill_svp) {
		ArtUta *uta1, *uta2, *uta3;
		uta1 = art_uta_from_svp (*stroke_svp);
		uta2 = art_uta_from_svp (*fill_svp);
		uta3 = art_uta_union (uta1, uta2);
		art_uta_free (uta1);
		art_uta_free (uta2);
		return uta3;
	} else if (stroke_svp)
		return art_uta_from_svp (*stroke_svp);
	else if (fill_svp)
		return art_uta_from_svp (*fill_svp);

	g_assert_not_reached();
	return NULL;
}

typedef struct {
	ArtSVP *stroke_svp;
	ArtSVP *fill_svp;
} PathHelper;

static void
reset_path_helper (PathHelper *helper, GnomeCanvasItem *item)
{
	ArtUta *repaint_uta;

	if (!helper)
		return;

	if (item && item->canvas && GTK_WIDGET_DRAWABLE (item->canvas)) {
		if (helper->stroke_svp) {
			repaint_uta = art_uta_from_svp (helper->stroke_svp);
			gnome_canvas_request_redraw_uta (item->canvas, repaint_uta);
		}
		if (helper->fill_svp) {
			repaint_uta = art_uta_from_svp (helper->fill_svp);
			gnome_canvas_request_redraw_uta (item->canvas, repaint_uta);
		}
	}
	if (helper->stroke_svp) {
		art_svp_free (helper->stroke_svp);
		helper->stroke_svp = NULL;
	}
	if (helper->fill_svp) {
		art_svp_free (helper->fill_svp);
		helper->fill_svp = NULL;
	}
}

/**
 * path_update:
 *
 * Update a VPath structure to create a SVP.
 * Code stolen from GnomeCanvasPolygon.
 **/
static inline ArtSVP*
path_update (DiaShapePath *shape, DiaCanvasViewItem *item,
	     double *affine, ArtSVP *clip, int flags,
	     DiaShapeViewInfo *view_info)
{
	ArtUta *repaint_uta;
	PathHelper *helper;
	ArtSVP *new_clip = NULL;

	g_assert (shape != NULL);
	g_assert (shape->shape.type == DIA_SHAPE_PATH);

	if (!view_info->data)
		helper = view_info->data = g_new0 (PathHelper, 1);
	else {
		GnomeCanvasItem *item = view_info->key;
		helper = view_info->data;
		reset_path_helper (helper, item);
	}
	view_info->free_func = path_free;

	repaint_uta = create_svps_from_vpath (shape->vpath,
					      shape->line_width,
					      shape->join,
					      shape->cap,
					      shape->cyclic,
					      shape->dash.dash ? &shape->dash : NULL,
					      affine,
					      clip,
					      DIA_COLOR_ALPHA (shape->shape.color) > 0 ? &helper->stroke_svp : NULL,
					      ((shape->fill != DIA_FILL_NONE) && (DIA_COLOR_ALPHA (shape->fill_color) > 0)) || shape->clipping ? &helper->fill_svp : NULL);

	if (shape->clipping) {
		if (clip)
			new_clip = art_svp_union (helper->fill_svp, clip);
		else if (shape->fill != DIA_FILL_NONE && DIA_COLOR_ALPHA (shape->fill_color) > 0) {
			new_clip = svp_dup (helper->fill_svp);
		} else {
			new_clip = helper->fill_svp;
			helper->fill_svp = NULL;
		}
	}

	if (repaint_uta)
		dia_canvas_view_item_request_redraw_uta (item, repaint_uta);

	return new_clip;
}

static inline void
path_render (DiaShape *shape, DiaCanvasViewItem *item,
	     GnomeCanvasBuf *buf, DiaShapeViewInfo *view_info)
{
	PathHelper *helper = view_info->data;
	if (!helper)
		return;

	if (helper->fill_svp) {
		DiaColor fill_color = 0;
		switch (shape->type) {
		case DIA_SHAPE_PATH:
			fill_color = ((DiaShapePath*) shape)->fill_color;
			break;
		case DIA_SHAPE_BEZIER:
			fill_color = ((DiaShapeBezier*) shape)->fill_color;
			break;
		case DIA_SHAPE_ELLIPSE:
			fill_color = ((DiaShapeEllipse*) shape)->fill_color;
			break;
		default:
			g_assert_not_reached();
		}
		gnome_canvas_render_svp (buf,
					 helper->fill_svp,
					 fill_color);
	}
	if (helper->stroke_svp) {
		gnome_canvas_render_svp (buf,
					 helper->stroke_svp,
					 shape->color);
	}
}

/*
 * Free the content of a path's and also request an update for the area
 * covered by the path.
 * Also redraw requests are emited here.
 * This function is used by paths, beziers and ellipses.
 */
static void
path_free (DiaShapeViewInfo *view_info)
{
	GnomeCanvasItem *item;
	PathHelper *helper;

	g_assert (view_info != NULL);

	item = view_info->key;
	helper = view_info->data;
	view_info->data = NULL;

	if (!helper)
		return;

	reset_path_helper (helper, item);
	g_free (helper);
}

/*
 * Bezier
 *
 * path_render() and path_free() are also used.
 */
static inline ArtSVP*
bezier_update (DiaShapeBezier *shape, DiaCanvasViewItem *item,
	       double *affine, ArtSVP *clip, int flags,
	       DiaShapeViewInfo *view_info)
{
	ArtVpath *vpath = NULL;
	ArtBpath *bpath = NULL;
	gdouble width = 0.0;
	ArtUta *repaint_uta = NULL;
	PathHelper *helper;
	ArtSVP *new_clip = NULL;

	g_assert (shape != NULL);
	g_assert (shape->shape.type == DIA_SHAPE_BEZIER);

	if (!view_info->data)
		helper = view_info->data = g_new0 (PathHelper, 1);
	else {
		GnomeCanvasItem *item = view_info->key;
		helper = view_info->data;
		reset_path_helper (helper, item);
	}
	view_info->free_func = path_free;

	if (affine) {
		bpath = art_bpath_affine_transform (shape->bpath,
						    affine);

		width = shape->line_width * art_affine_expansion (affine);
	} else {
		bpath = shape->bpath;
		width = shape->line_width;
	}

	vpath = art_bez_path_to_vec (bpath, 0.25);

	if (bpath != shape->bpath)
		art_free (bpath);

	repaint_uta = create_svps_from_vpath (vpath,
					      width,
					      shape->join,
					      shape->cap,
					      shape->cyclic,
					      NULL,
					      NULL,
					      clip,
					      DIA_COLOR_ALPHA (shape->shape.color) > 0 ? &helper->stroke_svp : NULL,
					      shape->fill != DIA_FILL_NONE && DIA_COLOR_ALPHA (shape->fill_color) > 0 ? &helper->fill_svp : NULL);

	/* Cleanup */
	art_free (vpath);

	if (shape->clipping) {
		if (clip)
			new_clip = art_svp_union (helper->fill_svp, clip);
		else if (shape->fill != DIA_FILL_NONE && DIA_COLOR_ALPHA (shape->fill_color) > 0) {
			new_clip = svp_dup (helper->fill_svp);
		} else {
			new_clip = helper->fill_svp;
			helper->fill_svp = NULL;
		}
	}

	if (repaint_uta)
		dia_canvas_view_item_request_redraw_uta (item, repaint_uta);

	return new_clip;
}

/*
 * Ellipse
 *
 * Uses path_render() and path_free().
 */

static inline ArtSVP*
ellipse_update (DiaShapeEllipse *shape, DiaCanvasViewItem *item,
		double *affine, ArtSVP *clip, int flags,
		DiaShapeViewInfo *view_info)
{
	static ArtVpath *ellipse = NULL;
	ArtUta *repaint_uta = NULL;
	gdouble a[6], b[6];
	gdouble line_width;
	PathHelper *helper;
	ArtSVP *new_clip = NULL;

	g_assert (shape != NULL);
	g_assert (shape->shape.type == DIA_SHAPE_ELLIPSE);

	if (!view_info->data)
		helper = view_info->data = g_new0 (PathHelper, 1);
	else {
		GnomeCanvasItem *item = view_info->key;
		helper = view_info->data;
		reset_path_helper (helper, item);
	}
	view_info->free_func = path_free;

	art_affine_scale (a, shape->width / 2.0, shape->height / 2.0);
	art_affine_translate (b, shape->center.x, shape->center.y);
	art_affine_multiply (a, a, b);

	if (!ellipse)
		ellipse = art_vpath_new_circle (0.0, 0.0, 1.0);

	if (affine) {
		line_width = shape->line_width / art_affine_expansion (a);
		art_affine_multiply (a, a, affine);
	} else {
		line_width = shape->line_width;
	}

	repaint_uta = create_svps_from_vpath (ellipse,
					      line_width,
					      ART_PATH_STROKE_JOIN_BEVEL,
					      ART_PATH_STROKE_CAP_BUTT,
					      FALSE,
					      NULL,
					      a,
					      clip,
					      DIA_COLOR_ALPHA (shape->shape.color) > 0 ? &helper->stroke_svp : NULL,
					      shape->fill != DIA_FILL_NONE && DIA_COLOR_ALPHA (shape->fill_color) > 0 ? &helper->fill_svp : NULL);

	/* Do not free ellipse, it's static! */

	if (shape->clipping) {
		if (clip)
			new_clip = art_svp_union (helper->fill_svp, clip);
		else if (shape->fill != DIA_FILL_NONE && DIA_COLOR_ALPHA (shape->fill_color) > 0) {
			new_clip = svp_dup (helper->fill_svp);
		} else {
			new_clip = helper->fill_svp;
			helper->fill_svp = NULL;
		}
	}

	if (repaint_uta)
		dia_canvas_view_item_request_redraw_uta (item, repaint_uta);

	return new_clip;
}

/*
 * Text
 */
typedef struct {
	ArtIRect bb;
	gdouble affine[6];
	FT_Bitmap bitmap;
	GSList *selection_box; /* List of ArtSVP's */
} TextHelper;

static void text_free (DiaShapeViewInfo *view_info);

#if 0
/* TODO: Remove this function, create a default function instead! */
static gchar*
next_char (const gchar *str, gboolean markup)
{
        if (markup) {
                switch (*str) {
                case '<':
                        while (*str == '<') {
                                while (*str != '>')
                                        str = g_utf8_next_char (str);
                                /* Point to charachter after the '>' */
                                str = g_utf8_next_char (str);
                        }
                        break;
                case '&':
                        /* Move to the end of the &xx; encoded character. */
                        while (*str != ';')
                                str = g_utf8_next_char (str);
                        break;
                default:
			break;
                }
        }
        return g_utf8_next_char (str);
}

static inline void
add_newlines_to_layout(PangoLayout *layout, const gchar *text,
		       gboolean markup)
{
	GSList *lines;
	PangoLayoutLine *line;
	GString *new_text = g_string_new(text);
	gint index, trailing;
	const char *pango_text;
	char *p = new_text->str;
	gint i;

	pango_text = pango_layout_get_text (layout);
	for (lines = pango_layout_get_lines (layout);
	     lines && lines->next; lines = lines->next) {
		line = lines->data;
		for (i = line->length; i > 0; i--)
			p = next_char (p, markup);
		if (*p != '\n') {
			g_string_insert_c (new_text, p - new_text->str, '\n');
			p++;
		}
	}
//	if (markup)
//		pango_layout_set_markup(layout, new_text->str, new_text->len);
//	else
//		pango_layout_set_text(layout, new_text->str, new_text->len);
	g_string_free (new_text, TRUE);
}
#endif

static inline ArtSVP*
text_update (DiaShapeText *shape, DiaCanvasViewItem *item,
	     double *affine, ArtSVP *clip, int flags,
	     DiaShapeViewInfo *view_info)
{
	TextHelper *helper;
	PangoLayout *layout;
	gint width, height, max_width, max_height, offset_x = 0;
	gint dwidth;
	ArtDRect dr;
	gint r, c, a;
	guchar alpha;
	gdouble scale;

	if (!shape->text)
		return NULL;

	/* Cleanup and initialization: */
	if (!view_info->data)
		helper = g_new0 (TextHelper, 1);
	else {
		helper = view_info->data;
		gnome_canvas_request_redraw (GNOME_CANVAS_ITEM (item)->canvas,
					     helper->bb.x0, helper->bb.y0,
					     helper->bb.x1, helper->bb.y1);
	}

	art_affine_multiply (helper->affine, shape->affine, affine);

	/* Find amount of scaling for the font size. If scale <= 1.0 the
	font is not resized, but the produced bitmap is scaled. */
	scale = art_affine_expansion (helper->affine);
	if (scale < 1e-4)
		return NULL;

	/* FixMe: Maybe we can fix the fonts by creating a new PangoContext
	 * with the right DPI information, instead of using 100dpi. */
//	if (scale > 1.0) {
//		max_width = (gint) ceil (shape->max_width * scale);
//		max_height = (gint) ceil (shape->max_height * scale);
//		/* compensate for the scaling of the font: */
//		art_affine_scale(tmp_a, 1 / scale, 1 / scale);
//		art_affine_multiply (helper->affine, tmp_a, helper->affine);
//		dia_shape_text_pretty_fill_pango_layout ((DiaShape*) shape,
//							 layout, scale);
//	} else {
//	}

	layout = dia_shape_art_get_pango_layout ();
	max_width = (gint) ceil (shape->max_width);
	max_height = (gint) ceil (shape->max_height);
	dia_shape_text_fill_pango_layout ((DiaShape*) shape, layout);

	//g_message (G_STRLOC"2ctx=%f scale=%f", 100.0 * scale, scale);
	//context = pango_ft2_get_context (10.0 * scale, 100.0 * scale);
	//layout = pango_layout_new (context);
	//g_object_unref (context);

	//max_width = (gint) ceil (shape->max_width * scale);
	//max_height = (gint) ceil (shape->max_height * scale);
	/* compensate for the scaling of the font: */
	//art_affine_scale(tmp_a, 1 / scale, 1 / scale);
	//art_affine_multiply (helper->affine, tmp_a, helper->affine);
	//dia_shape_text_fill_pango_layout ((DiaShape*) shape,
	//				  layout, 1.0);
	
	//add_newlines_to_layout (layout, shape->text, shape->markup);

	/* Create a bitmap to render the text on: */
	pango_layout_get_pixel_size (layout, &width, &height);
	//pango_layout_get_size (layout, &width, &height);
	//width = (gint) ceil ((((gdouble) width) / PANGO_SCALE) * scale);
	//height = (gint) ceil ((((gdouble) height) / PANGO_SCALE) * scale);

	dwidth = max_width - width;
	height = MIN (height, max_height);
	width = MIN (width, max_width);
	if ((height != helper->bitmap.rows)
	    || (width != helper->bitmap.width)) {
		helper->bitmap.rows =  height;
		helper->bitmap.width = width;
		/* pitch does the same as rowstride for LibArt functions. */
		helper->bitmap.pitch = (helper->bitmap.width+3)&~3;
		if (helper->bitmap.buffer) {
			helper->bitmap.buffer = g_realloc (helper->bitmap.buffer,
					helper->bitmap.rows * helper->bitmap.pitch);
			memset (helper->bitmap.buffer, 0,
				helper->bitmap.rows * helper->bitmap.pitch);
		} else {
			helper->bitmap.buffer = g_malloc0 (helper->bitmap.rows * helper->bitmap.pitch);
		}
		helper->bitmap.num_grays = 256;
		helper->bitmap.pixel_mode = ft_pixel_mode_grays;
	} else
		memset (helper->bitmap.buffer, 0,
			helper->bitmap.rows * helper->bitmap.pitch);

	/* Set the width to the buffers width. */
	if (shape->alignment != PANGO_ALIGN_LEFT) {
		gint w;
		pango_layout_get_size (layout, &w, NULL);
		pango_layout_set_width (layout, w);
	}

	/* Change offset depending on shape->alignment */
	if (shape->alignment == PANGO_ALIGN_LEFT) {
		offset_x = 0;
	} else if (shape->alignment == PANGO_ALIGN_CENTER) {
		gdouble a[6];
		art_affine_translate (a, (double) MAX(dwidth, 0) / 2.0, 0.0);
		art_affine_multiply (helper->affine, a, helper->affine);
		offset_x = MIN(dwidth, 0) / 2;
	} else if (shape->alignment == PANGO_ALIGN_RIGHT) {
		gdouble a[6];
		art_affine_translate (a, (double) MAX(dwidth, 0), 0.0);
		art_affine_multiply (helper->affine, a, helper->affine);
		offset_x = MIN(dwidth, 0);
	} else
		g_assert_not_reached ();
	
	/* Render the text: The result is not nice for scale factors > 1.0,
	 * since we resized the font. */
	pango_ft2_render_layout (&helper->bitmap, layout, offset_x, 0);

	/* And we also want text to be transparent, do we adjust the bitmap
	 * produced by pango_ft2_render_layout().
	 *
	 * FixMe: is this a nice fuction to apply alpha to text?
	 */
	alpha = shape->shape.color & 0xFF;
	if (alpha != 0xFF) {
		for (r = 0; r < helper->bitmap.rows; r++) {
			for (c = 0; c < helper->bitmap.width; c++) {
				a = helper->bitmap.buffer[r*helper->bitmap.pitch+c];
				if (a != 0) {
					helper->bitmap.buffer[r * helper->bitmap.pitch + c] = a - MIN (255 - alpha, a);
				}
			}
		}
	}

	dr.x0 = 0.0;
	dr.y0 = 0.0;
	dr.x1 = (gdouble) width;
	dr.y1 = (gdouble) height;

	art_drect_affine_transform (&dr, &dr, helper->affine);

	art_drect_to_irect (&helper->bb, &dr);

	gnome_canvas_request_redraw (GNOME_CANVAS_ITEM (item)->canvas,
				     helper->bb.x0, helper->bb.y0,
				     helper->bb.x1, helper->bb.y1);
	view_info->data = helper;
	view_info->free_func = text_free;

	g_object_unref (layout);

	return NULL;
}

static inline void
text_render (DiaShape *shape, DiaCanvasViewItem *item,
	     GnomeCanvasBuf *buf, DiaShapeViewInfo *view_info)
{
	TextHelper *helper = view_info->data;

	if (!view_info->data)
		return;

	art_rgb_a_affine (buf->buf,
			buf->rect.x0, buf->rect.y0,
			buf->rect.x1, buf->rect.y1,
			buf->buf_rowstride,
			helper->bitmap.buffer, helper->bitmap.width,
			helper->bitmap.rows, helper->bitmap.pitch,
			shape->color >> 8,
			helper->affine, ART_FILTER_NEAREST, NULL);
}

static void
text_free (DiaShapeViewInfo *view_info)
{
	TextHelper *helper = (TextHelper*) view_info->data;
	g_free (helper->bitmap.buffer);
	g_free (view_info->data);
	view_info->data = NULL;
}

/*
 * Image
 */
typedef struct {
	ArtIRect bb;
	gdouble affine[6];
	GdkPixbuf *pixbuf;
} ImageHelper;

static void image_free (DiaShapeViewInfo *view_info);

static inline ArtSVP*
image_update (DiaShapeImage *shape, DiaCanvasViewItem *item,
	      double *affine, ArtSVP *clip, int flags,
	      DiaShapeViewInfo *view_info)
{
	ImageHelper *helper;
	ArtDRect dr;
	GnomeCanvasItem *gitem = GNOME_CANVAS_ITEM (item);

	if (!shape->pixbuf)
		return NULL;

	if (!view_info->data)
		helper = g_new (ImageHelper, 1);
	else {
		helper = view_info->data;
		gnome_canvas_request_redraw (GNOME_CANVAS_ITEM (item)->canvas,
					     helper->bb.x0, helper->bb.y0,
					     helper->bb.x1, helper->bb.y1);
		if (helper->pixbuf) {
			gdk_pixbuf_unref (helper->pixbuf);
			helper->pixbuf = NULL;
		}
	}

	if (clip) {
		/* FixMe: We can't handle clipped images right now */
#if 0
		ArtDRect pix_bb;
		ArtDRect intersect;
		gdouble *a = shape->affine;
		gint width = gdk_pixbuf_get_width (shape->pixbuf);
		gint height = gdk_pixbuf_get_height (shape->pixbuf);

		pix_bb.x0 = a[4];
		pix_bb.y0 = a[5];
		pix_bb.x1 = width * a[0] + height * a[2] + a[4];
		pix_bb.y1 = width * a[1] + height * a[3] + a[5];
		art_drect_intersect (&intersect, &pix_bb, clip);
		if (art_drect_empty (&intersect)) {
			helper->pixbuf = NULL;
		} else {
			gdouble inv[6];
			art_affine_invert (inv, shape->affine);
			art_drect_affine_transform (&dr, &intersect, inv);
			helper->pixbuf = gdk_pixbuf_new_subpixbuf
						(shape->pixbuf,
						 MAX ((gint) (dr.x0 + 1), 0),
						 MAX ((gint) (dr.y0 + 1), 0),
						 (gint) (dr.x1 - dr.x0 - 1),
						 (gint) (dr.y1 - dr.y0 - 1));
		}
#endif
		dr.x0 = 0.0;
		dr.y0 = 0.0;
		dr.x1 = (gdouble) gdk_pixbuf_get_width (shape->pixbuf);
		dr.y1 = (gdouble) gdk_pixbuf_get_height (shape->pixbuf);
		helper->pixbuf = gdk_pixbuf_ref (shape->pixbuf);
	} else {
		dr.x0 = 0.0;
		dr.y0 = 0.0;
		dr.x1 = (gdouble) gdk_pixbuf_get_width (shape->pixbuf);
		dr.y1 = (gdouble) gdk_pixbuf_get_height (shape->pixbuf);
		helper->pixbuf = gdk_pixbuf_ref (shape->pixbuf);
	}

	art_affine_multiply (helper->affine, shape->affine, affine);
	art_drect_affine_transform (&dr, &dr, helper->affine);
	art_drect_to_irect (&helper->bb, &dr);

	gnome_canvas_request_redraw (gitem->canvas,
				     helper->bb.x0, helper->bb.y0,
				     helper->bb.x1, helper->bb.y1);

	view_info->free_func = image_free;
	view_info->data = helper;

	return NULL;
}

static inline void
image_render (DiaShape *shape, DiaCanvasViewItem *item,
	      GnomeCanvasBuf *buf, DiaShapeViewInfo *view_info)
{
	ImageHelper *helper = view_info->data;

	if (!helper->pixbuf
	    || (((DiaShapeImage*) shape)->affine[0] == 0.0)
	    || (((DiaShapeImage*) shape)->affine[3] == 0.0))
		return;

	if (gdk_pixbuf_get_has_alpha(helper->pixbuf))
		art_rgb_rgba_affine (buf->buf,
				     buf->rect.x0, buf->rect.y0,
				     buf->rect.x1, buf->rect.y1,
				     buf->buf_rowstride,
				     gdk_pixbuf_get_pixels (helper->pixbuf),
				     gdk_pixbuf_get_width (helper->pixbuf),
				     gdk_pixbuf_get_height (helper->pixbuf),
				     gdk_pixbuf_get_rowstride (helper->pixbuf),
				     helper->affine,
				     ART_FILTER_NEAREST,
				     NULL);
	else
		art_rgb_affine (buf->buf,
				buf->rect.x0, buf->rect.y0,
				buf->rect.x1, buf->rect.y1,
				buf->buf_rowstride,
				gdk_pixbuf_get_pixels (helper->pixbuf),
				gdk_pixbuf_get_width (helper->pixbuf),
				gdk_pixbuf_get_height (helper->pixbuf),
				gdk_pixbuf_get_rowstride (helper->pixbuf),
				helper->affine,
				ART_FILTER_NEAREST,
				NULL);
}

static void
image_free (DiaShapeViewInfo *view_info)
{
	if (view_info->data) {
		ImageHelper *helper = view_info->data;
		GnomeCanvasItem *item = view_info->key;
		gnome_canvas_request_redraw (item->canvas,
					     helper->bb.x0, helper->bb.y0,
					     helper->bb.x1, helper->bb.y1);
		if (helper->pixbuf)
			gdk_pixbuf_unref (helper->pixbuf);
		g_free (view_info->data);
		view_info->data = NULL;
	}
}


/**
 * dia_shape_art_update:
 * @shape: 
 * @item: 
 * @affine: affine transformation matrix
 * @clip: clipping path for the shape (may be %NULL)
 * @flags: flags...
 * 
 * This function is called for every CanvasViewItem. This way ViewItems are
 * notified that the need updating.
 *
 * Return value: A new clip path. The new clip path is to be used for all
 * elements that are rendered on top of @shape.
 **/
ArtSVP*
dia_shape_art_update (DiaShape *shape, DiaCanvasViewItem *item,
		      double *affine, ArtSVP *clip, int flags)
{
	ArtSVP *new_clip = NULL;
	DiaShapeViewInfo *view_info = NULL;
	gboolean is_new = FALSE;
	
	g_assert (DIA_IS_CANVAS_VIEW_ITEM (item));
	
	/* First, find the old shape-item-info (if none, create a new entry)
	 */
	view_info = dia_shape_get_view_info (shape, item);
	
	if (!view_info) {
		//g_message (__FUNCTION__": Creating new view info for %s (%p)",
		//	   g_type_name (item->item), item->item);
		view_info = g_new0 (DiaShapeViewInfo, 1);
		shape->view_info = g_list_append (shape->view_info, view_info);
		view_info->key = (gpointer) item;
		is_new = TRUE;
	} else if (!dia_shape_need_update (shape)
		   && !(GTK_OBJECT_FLAGS (item) & DIA_CANVAS_VIEW_ITEM_UPDATE_SHAPES))
			return NULL;
	
	g_assert (DIA_IS_CANVAS_VIEW_ITEM (view_info->key));
	
	switch (shape->type) {
	case DIA_SHAPE_PATH:
		new_clip = path_update ((DiaShapePath*) shape, item,
				        affine, clip, flags, view_info);
		break;
	case DIA_SHAPE_BEZIER:
		new_clip = bezier_update ((DiaShapeBezier*) shape, item,
				          affine, clip, flags, view_info);
		break;
	case DIA_SHAPE_ELLIPSE:
		new_clip = ellipse_update ((DiaShapeEllipse*) shape, item,
					   affine, clip, flags, view_info);
		break;
	case DIA_SHAPE_TEXT:
		new_clip = text_update ((DiaShapeText*) shape, item,
				        affine, clip, flags, view_info);
		break;
	case DIA_SHAPE_IMAGE:
		new_clip = image_update ((DiaShapeImage*) shape, item,
				         affine, clip, flags, view_info);
		break;
	default:
		g_warning ("No update method for shape of type %d",
			   shape->type);
		break;
	}
	
	if (!is_new)
		dia_shape_is_updated (shape);


	return new_clip;
}


void
dia_shape_art_render (DiaShape *shape, DiaCanvasViewItem *item,
		      GnomeCanvasBuf *buf)
{
	DiaShapeViewInfo *view_info;

	g_assert (DIA_IS_CANVAS_VIEW_ITEM (item));

	view_info = dia_shape_get_view_info  (shape, item);
	if (!view_info)
		return;
		
	g_assert (DIA_IS_CANVAS_VIEW_ITEM (view_info->key));
		
	switch (shape->type) {
	case DIA_SHAPE_PATH:
	case DIA_SHAPE_BEZIER:
	case DIA_SHAPE_ELLIPSE:
		path_render (shape, item, buf, view_info);
		break;
	case DIA_SHAPE_TEXT:
		text_render (shape, item, buf, view_info);
		break;
	case DIA_SHAPE_IMAGE:
		image_render (shape, item, buf, view_info);
		break;
	default:
		break;
	}
}
