/*
 * GooCanvas. Copyright (C) 2005 Damon Chaplin.
 * Released under the GNU LGPL license. See COPYING for details.
 *
 * goocanvastext.c - text item.
 */

/**
 * SECTION:goocanvastext
 * @Title: GooCanvasText
 * @Short_Description: a text item.
 *
 * GooCanvasText represents a text item.
 *
 * It is a subclass of #GooCanvasItemSimple and so inherits all of the style
 * properties such as "fill-color".
 *
 * It also implements the #GooCanvasItem interface, so you can use the
 * #GooCanvasItem functions such as goo_canvas_item_raise() and
 * goo_canvas_item_rotate().
 *
 * The #GooCanvasText:width and #GooCanvasText:height properties specify the
 * area of the item. If it exceeds that area because there is too much text,
 * it is clipped. The properties can be set to -1 to disable clipping.
 *
 * To create a #GooCanvasText use goo_canvas_text_new().
 *
 * To get or set the properties of an existing #GooCanvasText, use
 * g_object_get() and g_object_set().
 */
#include <config.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include "goocanvastext.h"
#include "goocanvas.h"

typedef struct _GooCanvasTextPrivate GooCanvasTextPrivate;
struct _GooCanvasTextPrivate {
  gdouble height;
};

#define GOO_CANVAS_TEXT_GET_PRIVATE(text) \
   (G_TYPE_INSTANCE_GET_PRIVATE ((text), GOO_TYPE_CANVAS_TEXT, GooCanvasTextPrivate))
#define GOO_CANVAS_TEXT_MODEL_GET_PRIVATE(text) \
   (G_TYPE_INSTANCE_GET_PRIVATE ((text), GOO_TYPE_CANVAS_TEXT_MODEL, GooCanvasTextPrivate))

enum {
  PROP_0,

  PROP_X,
  PROP_Y,
  PROP_WIDTH,
  PROP_HEIGHT,
  PROP_TEXT,
  PROP_USE_MARKUP,
  PROP_ANCHOR,
  PROP_ALIGN,
  PROP_ELLIPSIZE,
  PROP_WRAP
};

static PangoLayout*
goo_canvas_text_create_layout (GooCanvasItemSimpleData *simple_data,
			       GooCanvasTextData       *text_data,
			       gdouble                  layout_width,
			       cairo_t                 *cr,
			       GooCanvasBounds         *bounds,
			       gdouble	               *origin_x_return,
			       gdouble	               *origin_y_return);

static void goo_canvas_text_finalize     (GObject            *object);
static void canvas_item_interface_init   (GooCanvasItemIface *iface);
static void goo_canvas_text_get_property (GObject            *object,
					  guint               param_id,
					  GValue             *value,
					  GParamSpec         *pspec);
static void goo_canvas_text_set_property (GObject            *object,
					  guint               param_id,
					  const GValue       *value,
					  GParamSpec         *pspec);

G_DEFINE_TYPE_WITH_CODE (GooCanvasText, goo_canvas_text,
			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
						canvas_item_interface_init))


static void
goo_canvas_text_install_common_properties (GObjectClass *gobject_class)
{
  /* Text */
  g_object_class_install_property (gobject_class, PROP_TEXT,
				   g_param_spec_string ("text",
							_("Text"),
							_("The text to display"),
							NULL,
							G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_USE_MARKUP,
				   g_param_spec_boolean ("use-markup",
							 _("Use Markup"),
							 _("Whether to parse PangoMarkup in the text, to support different styles"),
							 FALSE,
							 G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_ELLIPSIZE,
                                   g_param_spec_enum ("ellipsize",
                                                      _("Ellipsize"),
                                                      _("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string"),
						      PANGO_TYPE_ELLIPSIZE_MODE,
						      PANGO_ELLIPSIZE_NONE,
                                                      G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_WRAP,
                                   g_param_spec_enum ("wrap",
                                                      _("Wrap"),
                                                      _("The preferred method of wrapping the string if a width has been set"),
						      PANGO_TYPE_WRAP_MODE,
						      PANGO_WRAP_WORD,
                                                      G_PARAM_READWRITE));

  /* Position */
  g_object_class_install_property (gobject_class, PROP_X,
				   g_param_spec_double ("x",
							"X",
							_("The x coordinate of the text"),
							-G_MAXDOUBLE,
							G_MAXDOUBLE, 0.0,
							G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_Y,
				   g_param_spec_double ("y",
							"Y",
							_("The y coordinate of the text"),
							-G_MAXDOUBLE,
							G_MAXDOUBLE, 0.0,
							G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_WIDTH,
				   g_param_spec_double ("width",
							_("Width"),
							_("The width to use to layout the text, or -1 to let the text use as much horizontal space as needed"),
							-G_MAXDOUBLE,
							G_MAXDOUBLE, -1.0,
							G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_HEIGHT,
				   g_param_spec_double ("height",
							_("Height"),
							_("The height to use to layout the text, or -1 to let the text use as much vertical space as needed"),
							-G_MAXDOUBLE,
							G_MAXDOUBLE, -1.0,
							G_PARAM_READWRITE));


  g_object_class_install_property (gobject_class, PROP_ANCHOR,
				   g_param_spec_enum ("anchor",
						      _("Anchor"),
						      _("How to position the text relative to the given x and y coordinates"),
						      GOO_TYPE_CANVAS_ANCHOR_TYPE,
						      GOO_CANVAS_ANCHOR_NW,
						      G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class, PROP_ALIGN,
				   g_param_spec_enum ("alignment",
						      _("Alignment"),
						      _("How to align the text"),
						      PANGO_TYPE_ALIGNMENT,
						      PANGO_ALIGN_LEFT,
						      G_PARAM_READWRITE));
}


static void
goo_canvas_text_init (GooCanvasText *text)
{
  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_GET_PRIVATE (text);

  text->text_data = g_slice_new0 (GooCanvasTextData);
  text->text_data->width = -1.0;
  text->text_data->anchor = GOO_CANVAS_ANCHOR_NW;
  text->text_data->ellipsize = PANGO_ELLIPSIZE_NONE;
  text->text_data->wrap = PANGO_WRAP_WORD;

  text->layout_width = -1.0;

  priv->height = -1.0;
}


/**
 * goo_canvas_text_new:
 * @parent: (skip): the parent item, or %NULL. If a parent is specified, it will assume
 *  ownership of the item, and the item will automatically be freed when it is
 *  removed from the parent. Otherwise call g_object_unref() to free it.
 * @string: the text to display.
 * @x: the x coordinate of the text.
 * @y: the y coordinate of the text.
 * @width: the width of the text item, or -1 for unlimited width.
 * @anchor: the position of the text relative to the given @x and @y
 *  coordinates. For example an anchor of %GDK_ANCHOR_NW will result in the
 *  top-left of the text being placed at the given @x and @y coordinates.
 *  An anchor of %GDK_ANCHOR_CENTER will result in the center of the text being
 *  placed at the @x and @y coordinates.
 * @...: optional pairs of property names and values, and a terminating %NULL.
 * 
 * Creates a new text item.
 * 
 * <!--PARAMETERS-->
 *
 * Here's an example showing how to create a text item with the bottom right
 * of the text box placed at (500,500):
 *
 * <informalexample><programlisting>
 *  GooCanvasItem *text = goo_canvas_text_new (mygroup, "Hello World", 500.0, 500.0, 200.0, GOO_CANVAS_ANCHOR_SE,
 *                                             "fill-color", "blue",
 *                                             NULL);
 * </programlisting></informalexample>
 * 
 * Returns: (transfer full): a new text item.
 **/
GooCanvasItem*
goo_canvas_text_new (GooCanvasItem *parent,
		     const char    *string,
		     gdouble        x,
		     gdouble        y,
		     gdouble        width,
		     GooCanvasAnchorType  anchor,
		     ...)
{
  GooCanvasItem *item;
  GooCanvasText *text;
  GooCanvasTextData *text_data;
  const char *first_property;
  va_list var_args;

  item = g_object_new (GOO_TYPE_CANVAS_TEXT, NULL);
  text = (GooCanvasText*) item;

  text_data = text->text_data;
  text_data->text = g_strdup (string);
  text_data->x = x;
  text_data->y = y;
  text_data->width = width;
  text_data->anchor = anchor;

  va_start (var_args, anchor);
  first_property = va_arg (var_args, char*);
  if (first_property)
    g_object_set_valist ((GObject*) item, first_property, var_args);
  va_end (var_args);

  if (parent)
    {
      goo_canvas_item_add_child (parent, item, -1);
      g_object_unref (item);
    }

  return item;
}


static void
goo_canvas_text_finalize (GObject *object)
{
  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
  GooCanvasText *text = (GooCanvasText*) object;

  /* Free our data if we didn't have a model. (If we had a model it would
     have been reset in dispose() and simple_data will be NULL.) */
  if (simple->simple_data)
    {
      g_free (text->text_data->text);
      g_slice_free (GooCanvasTextData, text->text_data);
    }
  text->text_data = NULL;

  G_OBJECT_CLASS (goo_canvas_text_parent_class)->finalize (object);
}


/* Gets the private data to use, from the model or from the item itself. */
static GooCanvasTextPrivate*
goo_canvas_text_get_private (GooCanvasText *text)
{
  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) text;

  if (simple->model)
    return GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (simple->model);
  else
    return GOO_CANVAS_TEXT_GET_PRIVATE (text);
}


static void
goo_canvas_text_get_common_property (GObject                 *object,
				     GooCanvasTextData       *text_data,
				     GooCanvasTextPrivate    *priv,
				     guint                    prop_id,
				     GValue                  *value,
				     GParamSpec              *pspec)
{
  switch (prop_id)
    {
    case PROP_X:
      g_value_set_double (value, text_data->x);
      break;
    case PROP_Y:
      g_value_set_double (value, text_data->y);
      break;
    case PROP_WIDTH:
      g_value_set_double (value, text_data->width);
      break;
    case PROP_HEIGHT:
      g_value_set_double (value, priv->height);
      break;
    case PROP_TEXT:
      g_value_set_string (value, text_data->text);
      break;
    case PROP_USE_MARKUP:
      g_value_set_boolean (value, text_data->use_markup);
      break;
    case PROP_ELLIPSIZE:
      g_value_set_enum (value, text_data->ellipsize);
      break;
    case PROP_WRAP:
      g_value_set_enum (value, text_data->wrap);
      break;
    case PROP_ANCHOR:
      g_value_set_enum (value, text_data->anchor);
      break;
    case PROP_ALIGN:
      g_value_set_enum (value, text_data->alignment);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}


static void
goo_canvas_text_get_property (GObject              *object,
			      guint                 prop_id,
			      GValue               *value,
			      GParamSpec           *pspec)
{
  GooCanvasText *text = (GooCanvasText*) object;
  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);

  goo_canvas_text_get_common_property (object, text->text_data, priv,
                                       prop_id, value, pspec);
}


static void
goo_canvas_text_set_common_property (GObject                 *object,
				     GooCanvasTextData       *text_data,
				     GooCanvasTextPrivate    *priv,
				     guint                    prop_id,
				     const GValue            *value,
				     GParamSpec              *pspec)
{
  switch (prop_id)
    {
    case PROP_X:
      text_data->x = g_value_get_double (value);
      break;
    case PROP_Y:
      text_data->y = g_value_get_double (value);
      break;
    case PROP_WIDTH:
      text_data->width = g_value_get_double (value);
      break;
    case PROP_HEIGHT:
      priv->height = g_value_get_double (value);
      break;
    case PROP_TEXT:
      g_free (text_data->text);
      text_data->text = g_value_dup_string (value);
      break;
    case PROP_USE_MARKUP:
      text_data->use_markup = g_value_get_boolean (value);
      break;
    case PROP_ELLIPSIZE:
      text_data->ellipsize = g_value_get_enum (value);
      break;
    case PROP_WRAP:
      text_data->wrap = g_value_get_enum (value);
      break;
    case PROP_ANCHOR:
      text_data->anchor = g_value_get_enum (value);
      break;
    case PROP_ALIGN:
      text_data->alignment = g_value_get_enum (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}


static void
goo_canvas_text_set_property (GObject              *object,
			      guint                 prop_id,
			      const GValue         *value,
			      GParamSpec           *pspec)
{
  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
  GooCanvasText *text = (GooCanvasText*) object;
  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);

  if (simple->model)
    {
      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
      return;
    }

  goo_canvas_text_set_common_property (object, text->text_data, priv, prop_id,
				       value, pspec);
  goo_canvas_item_simple_changed (simple, TRUE);
}


static PangoLayout*
goo_canvas_text_create_layout (GooCanvasItemSimpleData *simple_data,
			       GooCanvasTextData       *text_data,
			       gdouble                  layout_width,
			       cairo_t                 *cr,
			       GooCanvasBounds         *bounds,
			       gdouble	               *origin_x_return,
			       gdouble	               *origin_y_return)
{
  GooCanvasStyle *style = simple_data->style;
  GValue *svalue;
  PangoLayout *layout;
  PangoContext *context;
  PangoRectangle ink_rect, logical_rect;
  double logical_width, logical_height, align_width, origin_x, origin_y;
  gchar *string;
  double x1_extension, x2_extension, y1_extension, y2_extension;
  cairo_font_options_t *font_options;
  cairo_hint_metrics_t hint_metrics = CAIRO_HINT_METRICS_OFF;

  string = text_data->text ? text_data->text : "";

  layout = pango_cairo_create_layout (cr);
  context = pango_layout_get_context (layout);

  if (layout_width > 0)
    pango_layout_set_width (layout, (double) layout_width * PANGO_SCALE);

  if (text_data->use_markup)
    pango_layout_set_markup (layout, string, -1);
  else
    pango_layout_set_text (layout, string, -1);

  svalue = goo_canvas_style_get_property (style,
					  goo_canvas_style_font_desc_id);
  if (svalue)
    pango_layout_set_font_description (layout, svalue->data[0].v_pointer);

  svalue = goo_canvas_style_get_property (style,
					  goo_canvas_style_hint_metrics_id);
  if (svalue)
    hint_metrics = svalue->data[0].v_long;

  font_options = cairo_font_options_create ();
  cairo_font_options_set_hint_metrics (font_options, hint_metrics);
  pango_cairo_context_set_font_options (context, font_options);
  cairo_font_options_destroy (font_options);

  if (text_data->alignment != PANGO_ALIGN_LEFT)
    pango_layout_set_alignment (layout, text_data->alignment);

  pango_layout_set_ellipsize (layout, text_data->ellipsize);

  pango_layout_set_wrap (layout, text_data->wrap);

  if (bounds)
    {
      /* Get size of the text, so we can position it according to anchor. */
      pango_layout_get_extents (layout, &ink_rect, &logical_rect);

      logical_width = (double) logical_rect.width / PANGO_SCALE;
      logical_height = (double) logical_rect.height / PANGO_SCALE;

      /* If the text width has been set, that width is used to do the alignment
	 positioning. Otherwise the actual width is used. */
      if (layout_width > 0)
	align_width = layout_width;
      else
	align_width = logical_width;

      /* Now calculate the origin of the text, i.e. where we will tell Pango
	 to draw it. */
      origin_x = text_data->x;
      origin_y = text_data->y;

      switch (text_data->anchor)
	{
	case GOO_CANVAS_ANCHOR_N:
	case GOO_CANVAS_ANCHOR_CENTER:
	case GOO_CANVAS_ANCHOR_S:
	  origin_x -= align_width / 2.0;
	break;
	case GOO_CANVAS_ANCHOR_NE:
	case GOO_CANVAS_ANCHOR_E:
	case GOO_CANVAS_ANCHOR_SE:
	  origin_x -= align_width;
	  break;
	default:
	  break;
	}

      switch (text_data->anchor)
	{
	case GOO_CANVAS_ANCHOR_W:
	case GOO_CANVAS_ANCHOR_CENTER:
	case GOO_CANVAS_ANCHOR_E:
	  origin_y -= logical_height / 2.0;
	  break;
	case GOO_CANVAS_ANCHOR_SW:
	case GOO_CANVAS_ANCHOR_S:
	case GOO_CANVAS_ANCHOR_SE:
	  origin_y -= logical_height;
	  break;
	default:
	  break;
	}

      /* Return the origin of the text if required. */
      if (origin_x_return)
	*origin_x_return = origin_x;
      if (origin_y_return)
	*origin_y_return = origin_y;

      /* Now calculate the logical bounds. */
      bounds->x1 = origin_x;
      bounds->y1 = origin_y;

      if (layout_width > 0)
	{
	  /* If the text width has been set, and the alignment isn't
	     PANGO_ALIGN_LEFT, we need to adjust for the difference between
	     the actual width of the text and the width that was used for
	     alignment. */
	  switch (text_data->alignment)
	    {
	    case PANGO_ALIGN_CENTER:
	      bounds->x1 += (align_width - logical_width) / 2.0;
	      break;
	    case PANGO_ALIGN_RIGHT:
	      bounds->x1 += align_width - logical_width;
	      break;
	    default:
	      break;
	    }
	}

      bounds->x2 = bounds->x1 + logical_width;
      bounds->y2 = bounds->y1 + logical_height;

      /* Now adjust it to take into account the ink bounds. Calculate how far
	 the ink rect extends outside each edge of the logical rect and adjust
	 the bounds as necessary. */
      x1_extension = logical_rect.x - ink_rect.x;
      if (x1_extension > 0)
	bounds->x1 -= x1_extension / PANGO_SCALE;

      x2_extension = (ink_rect.x + ink_rect.width)
	- (logical_rect.x + logical_rect.width);
      if (x2_extension > 0)
	bounds->x2 += x2_extension / PANGO_SCALE;

      y1_extension = logical_rect.y - ink_rect.y;
      if (y1_extension > 0)
	bounds->y1 -= y1_extension / PANGO_SCALE;

      y2_extension = (ink_rect.y + ink_rect.height)
	- (logical_rect.y + logical_rect.height);
      if (y2_extension > 0)
	bounds->y2 += y2_extension / PANGO_SCALE;
    }

  return layout;
}


static void
goo_canvas_text_update  (GooCanvasItemSimple *simple,
			 cairo_t             *cr)
{
  GooCanvasText *text = (GooCanvasText*) simple;
  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
  PangoLayout *layout;

  /* Initialize the layout width to the text item's specified width property.
     It may get changed later in get_requested_height() according to the
     layout container and settings. */
  text->layout_width = text->text_data->width;

  /* Compute the new bounds. */
  layout = goo_canvas_text_create_layout (simple->simple_data, text->text_data,
					  text->layout_width, cr,
					  &simple->bounds, NULL, NULL);
  g_object_unref (layout);

  /* If the height is set, use that. */
  if (priv->height > 0.0)
    simple->bounds.y2 = simple->bounds.y1 + priv->height;
}


static gboolean
goo_canvas_text_is_unpainted (GooCanvasStyle *style)
{
  GValue *value;

  value = goo_canvas_style_get_property (style,
					 goo_canvas_style_fill_pattern_id);

  /* We only return TRUE if the value is explicitly set to NULL. */
  if (value && !value->data[0].v_pointer)
    return TRUE;
  return FALSE;
}


static gboolean
goo_canvas_text_is_item_at (GooCanvasItemSimple *simple,
			    gdouble              x,
			    gdouble              y,
			    cairo_t             *cr,
			    gboolean             is_pointer_event)
{
  GooCanvasItemSimpleData *simple_data = simple->simple_data;
  GooCanvasText *text = (GooCanvasText*) simple;
  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
  PangoLayout *layout;
  GooCanvasBounds bounds;
  PangoLayoutIter *iter;
  PangoRectangle ink_rect, log_rect;
  int px, py, x1, y1, x2, y2;
  int log_x2, ink_x2, log_y2, ink_y2;
  gdouble origin_x, origin_y;
  gboolean in_item = FALSE;

  /* If there is no text just return. */
  if (!text->text_data->text || !text->text_data->text[0])
    return FALSE;

  if (is_pointer_event
      && simple_data->pointer_events & GOO_CANVAS_EVENTS_PAINTED_MASK
      && goo_canvas_text_is_unpainted (simple_data->style))
    return FALSE;

  /* Check if the point is outside the clipped height. */
  if (priv->height > 0.0 && y > priv->height)
    return FALSE;

  layout = goo_canvas_text_create_layout (simple_data, text->text_data,
					  text->layout_width, cr, &bounds,
					  &origin_x, &origin_y);

  /* Convert the coordinates into Pango units. */
  px = (x - origin_x) * PANGO_SCALE;
  py = (y - origin_y) * PANGO_SCALE;

  /* We use line extents here. Note that SVG uses character cells to determine
     hits so we have slightly different behavior. */
  iter = pango_layout_get_iter (layout);
  do
    {
      pango_layout_iter_get_line_extents (iter, &ink_rect, &log_rect);

      /* We use a union of the ink rect and the logical rect, as we want to
	 let the user click on any part of the ink, even if it extends outside
	 the character cell (i.e. the ink rect), or click on the space in the
	 character cell (i.e. the logical rect). */
      x1 = MIN (log_rect.x, ink_rect.x);
      y1 = MIN (log_rect.y, ink_rect.y);

      log_x2 = log_rect.x + log_rect.width;
      ink_x2 = ink_rect.x + ink_rect.width;
      x2 = MAX (log_x2, ink_x2);

      log_y2 = log_rect.y + log_rect.height;
      ink_y2 = ink_rect.y + ink_rect.height;
      y2 = MAX (log_y2, ink_y2);

      if (px >= x1 && px < x2 && py >= y1 && py < y2)
	{
	  in_item = TRUE;
	  break;
	}

    } while (pango_layout_iter_next_line (iter));

  pango_layout_iter_free (iter);

  g_object_unref (layout);

  return in_item;
}


static void
goo_canvas_text_paint (GooCanvasItemSimple   *simple,
		       cairo_t               *cr,
		       const GooCanvasBounds *bounds)
{
  GooCanvasText *text = (GooCanvasText*) simple;
  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
  PangoLayout *layout;
  GooCanvasBounds layout_bounds;
  gdouble origin_x, origin_y;

  /* If there is no text just return. */
  if (!text->text_data->text || !text->text_data->text[0])
    return;

  goo_canvas_style_set_fill_options (simple->simple_data->style, cr);

  cairo_new_path (cr);
  layout = goo_canvas_text_create_layout (simple->simple_data, text->text_data,
					  text->layout_width, cr,
					  &layout_bounds,
					  &origin_x, &origin_y);
  cairo_save (cr);

  if (priv->height > 0.0)
    {
      cairo_rectangle (cr, origin_x, origin_y,
		       text->layout_width, priv->height);
      cairo_clip (cr);
    }
  cairo_move_to (cr, origin_x, origin_y);
  pango_cairo_show_layout (cr, layout);

  cairo_restore (cr);
  g_object_unref (layout);
}


static gboolean
goo_canvas_text_get_requested_area_for_width (GooCanvasItem	*item,
					      cairo_t		*cr,
					      gdouble            width,
					      GooCanvasBounds	*requested_area)
{
  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
  GooCanvasItemSimpleData *simple_data = simple->simple_data;
  GooCanvasText *text = (GooCanvasText*) item;
  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
  PangoLayout *layout;
  cairo_matrix_t matrix;
  double x_offset, y_offset;

  /* If we have a transformation besides a simple scale & translation, just
     return -1 as we can't adjust the height in that case. */
  if (simple_data->clip_path_commands
      || (simple_data->transform && (simple_data->transform->xy != 0.0
				     || simple_data->transform->yx != 0.0)))
    return FALSE;

  cairo_save (cr);
  if (simple_data->transform)
    cairo_transform (cr, simple_data->transform);

  /* Remove any current translation, to avoid the 16-bit cairo limit. */
  cairo_get_matrix (cr, &matrix);
  x_offset = matrix.x0;
  y_offset = matrix.y0;
  matrix.x0 = matrix.y0 = 0.0;
  cairo_set_matrix (cr, &matrix);

  /* Convert the width from the parent's coordinate space. Note that we only
     need to support a simple scale operation here. */
  text->layout_width = width;
  if (simple_data->transform)
    text->layout_width /= simple_data->transform->xx;

  /* Create layout with given width. */
  layout = goo_canvas_text_create_layout (simple_data, text->text_data,
					  text->layout_width, cr,
					  &simple->bounds, NULL, NULL);
  g_object_unref (layout);

  /* If the height is set, use that. */
  if (priv->height > 0.0)
    simple->bounds.y2 = simple->bounds.y1 + priv->height;


  /* Convert to device space. */
  cairo_user_to_device (cr, &simple->bounds.x1, &simple->bounds.y1);
  cairo_user_to_device (cr, &simple->bounds.x2, &simple->bounds.y2);

  /* Add the translation back to the bounds. */
  simple->bounds.x1 += x_offset;
  simple->bounds.y1 += y_offset;
  simple->bounds.x2 += x_offset;
  simple->bounds.y2 += y_offset;

  /* Restore the item's proper transformation matrix. */
  matrix.x0 = x_offset;
  matrix.y0 = y_offset;
  cairo_set_matrix (cr, &matrix);

  /* Convert back to user space. */
  cairo_device_to_user (cr, &simple->bounds.x1, &simple->bounds.y1);
  cairo_device_to_user (cr, &simple->bounds.x2, &simple->bounds.y2);


  /* Copy the user bounds to the requested area. */
  *requested_area = simple->bounds;

  /* Convert to the parent's coordinate space. */
  goo_canvas_item_simple_user_bounds_to_parent (simple, cr, requested_area);

  /* Convert the item's bounds to device space. */
  goo_canvas_item_simple_user_bounds_to_device (simple, cr, &simple->bounds);

#if 0
  g_print ("Width: %g Requested bounds: %g,%g - %g,%g\n",
	   width, simple->bounds.x1, simple->bounds.y1,
	   simple->bounds.x2, simple->bounds.y2);
#endif

  cairo_restore (cr);

  return TRUE;
}


/**
 * goo_canvas_text_get_natural_extents:
 * @text: a #GooCanvasText.
 * @ink_rect: the location to return the ink rect, or %NULL.
 * @logical_rect: the location to return the logical rect, or %NULL.
 * 
 * Gets the natural extents of the text, in the text item's coordinate space.
 *
 * The final extents of the text may be different, if the text item is placed
 * in a layout container such as #GooCanvasTable.
 **/
void
goo_canvas_text_get_natural_extents (GooCanvasText  *text,
				     PangoRectangle *ink_rect,
				     PangoRectangle *logical_rect)
{
  GooCanvasItem *item = (GooCanvasItem*) text;
  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) text;
  PangoLayout *layout;
  cairo_t *cr;

  if (simple->need_update)
    goo_canvas_item_ensure_updated (item);

  cr = goo_canvas_create_cairo_context (simple->canvas);
  layout = goo_canvas_text_create_layout (simple->simple_data, text->text_data,
					  text->text_data->width, cr, NULL,
					  NULL, NULL);
  pango_layout_get_extents (layout, ink_rect, logical_rect);
  g_object_unref (layout);
  cairo_destroy (cr);
}


static void
goo_canvas_text_set_model    (GooCanvasItem      *item,
			      GooCanvasItemModel *model)
{
  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
  GooCanvasText *text = (GooCanvasText*) item;
  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) model;

  /* If our text_data was allocated, free it. */
  if (!simple->model)
    g_slice_free (GooCanvasTextData, text->text_data);

  /* Now use the new model's text_data instead. */
  text->text_data = &tmodel->text_data;

  /* Let the parent GooCanvasItemSimple code do the rest. */
  goo_canvas_item_simple_set_model (simple, model);
}


static void
canvas_item_interface_init (GooCanvasItemIface *iface)
{
  iface->get_requested_area_for_width = goo_canvas_text_get_requested_area_for_width;
  iface->set_model            = goo_canvas_text_set_model;
}


static void
goo_canvas_text_class_init (GooCanvasTextClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass*) klass;
  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;

  g_type_class_add_private (gobject_class, sizeof (GooCanvasTextPrivate));

  gobject_class->finalize = goo_canvas_text_finalize;

  gobject_class->get_property = goo_canvas_text_get_property;
  gobject_class->set_property = goo_canvas_text_set_property;

  simple_class->simple_update        = goo_canvas_text_update;
  simple_class->simple_paint         = goo_canvas_text_paint;
  simple_class->simple_is_item_at    = goo_canvas_text_is_item_at;

  goo_canvas_text_install_common_properties (gobject_class);
}



/**
 * SECTION:goocanvastextmodel
 * @Title: GooCanvasTextModel
 * @Short_Description: a model for text items.
 *
 * GooCanvasTextModel represents a model for text items.
 *
 * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
 * style properties such as "fill-color".
 *
 * It also implements the #GooCanvasItemModel interface, so you can use the
 * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
 * goo_canvas_item_model_rotate().
 *
 * To create a #GooCanvasTextModel use goo_canvas_text_model_new().
 *
 * To get or set the properties of an existing #GooCanvasTextModel, use
 * g_object_get() and g_object_set().
 *
 * To respond to events such as mouse clicks on the text item you must connect
 * to the signal handlers of the corresponding #GooCanvasText objects.
 * (See goo_canvas_get_item() and #GooCanvas::item-created.)
 */

static void item_model_interface_init (GooCanvasItemModelIface *iface);
static void goo_canvas_text_model_finalize     (GObject            *object);
static void goo_canvas_text_model_get_property (GObject            *object,
						guint               param_id,
						GValue             *value,
						GParamSpec         *pspec);
static void goo_canvas_text_model_set_property (GObject            *object,
						guint               param_id,
						const GValue       *value,
						GParamSpec         *pspec);

G_DEFINE_TYPE_WITH_CODE (GooCanvasTextModel, goo_canvas_text_model,
			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
						item_model_interface_init))


static void
goo_canvas_text_model_class_init (GooCanvasTextModelClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass*) klass;

  g_type_class_add_private (gobject_class, sizeof (GooCanvasTextPrivate));

  gobject_class->finalize     = goo_canvas_text_model_finalize;

  gobject_class->get_property = goo_canvas_text_model_get_property;
  gobject_class->set_property = goo_canvas_text_model_set_property;

  goo_canvas_text_install_common_properties (gobject_class);
}


static void
goo_canvas_text_model_init (GooCanvasTextModel *tmodel)
{
  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (tmodel);

  tmodel->text_data.width = -1.0;
  tmodel->text_data.anchor = GOO_CANVAS_ANCHOR_NW;
  tmodel->text_data.ellipsize = PANGO_ELLIPSIZE_NONE;
  tmodel->text_data.wrap = PANGO_WRAP_WORD;

  priv->height = -1.0;
}


/**
 * goo_canvas_text_model_new:
 * @parent: (skip): the parent model, or %NULL. If a parent is specified, it will
 *  assume ownership of the item, and the item will automatically be freed when
 *  it is removed from the parent. Otherwise call g_object_unref() to free it.
 * @string: the text to display.
 * @x: the x coordinate of the text.
 * @y: the y coordinate of the text.
 * @width: the width of the text item, or -1 for unlimited width.
 * @anchor: the position of the text relative to the given @x and @y
 *  coordinates. For example an anchor of %GDK_ANCHOR_NW will result in the
 *  top-left of the text being placed at the given @x and @y coordinates.
 *  An anchor of %GDK_ANCHOR_CENTER will result in the center of the text being
 *  placed at the @x and @y coordinates.
 * @...: optional pairs of property names and values, and a terminating %NULL.
 * 
 * Creates a new text model.
 * 
 * <!--PARAMETERS-->
 *
 * Here's an example showing how to create a text item with the bottom right
 * of the text box placed at (500,500):
 *
 * <informalexample><programlisting>
 *  GooCanvasItemModel *text = goo_canvas_text_model_new (mygroup, "Hello World", 500.0, 500.0, 200.0, GOO_CANVAS_ANCHOR_SE,
 *                                                        "fill-color", "blue",
 *                                                        NULL);
 * </programlisting></informalexample>
 * 
 * Returns: (transfer full): a new text model.
 **/
GooCanvasItemModel*
goo_canvas_text_model_new (GooCanvasItemModel *parent,
			   const char         *string,
			   gdouble             x,
			   gdouble             y,
			   gdouble             width,
			   GooCanvasAnchorType       anchor,
			   ...)
{
  GooCanvasItemModel *model;
  GooCanvasTextModel *tmodel;
  GooCanvasTextData *text_data;
  const char *first_property;
  va_list var_args;

  model = g_object_new (GOO_TYPE_CANVAS_TEXT_MODEL, NULL);
  tmodel = (GooCanvasTextModel*) model;

  text_data = &tmodel->text_data;
  text_data->text = g_strdup (string);
  text_data->x = x;
  text_data->y = y;
  text_data->width = width;
  text_data->anchor = anchor;

  va_start (var_args, anchor);
  first_property = va_arg (var_args, char*);
  if (first_property)
    g_object_set_valist ((GObject*) model, first_property, var_args);
  va_end (var_args);

  if (parent)
    {
      goo_canvas_item_model_add_child (parent, model, -1);
      g_object_unref (model);
    }

  return model;
}


static void
goo_canvas_text_model_finalize (GObject *object)
{
  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) object;

  g_free (tmodel->text_data.text);

  G_OBJECT_CLASS (goo_canvas_text_model_parent_class)->finalize (object);
}


static void
goo_canvas_text_model_get_property (GObject              *object,
				    guint                 prop_id,
				    GValue               *value,
				    GParamSpec           *pspec)
{
  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) object;
  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (tmodel);

  goo_canvas_text_get_common_property (object, &tmodel->text_data, priv,
				       prop_id, value, pspec);
}


static void
goo_canvas_text_model_set_property (GObject              *object,
				    guint                 prop_id,
				    const GValue         *value,
				    GParamSpec           *pspec)
{
  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) object;
  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (tmodel);

  goo_canvas_text_set_common_property (object, &tmodel->text_data, priv,
				       prop_id, value, pspec);
  g_signal_emit_by_name (tmodel, "changed", TRUE);
}


static GooCanvasItem*
goo_canvas_text_model_create_item (GooCanvasItemModel *model,
				   GooCanvas          *canvas)
{
  GooCanvasItem *item;

  item = g_object_new (GOO_TYPE_CANVAS_TEXT, NULL);
  goo_canvas_item_set_model (item, model);

  return item;
}


static void
item_model_interface_init (GooCanvasItemModelIface *iface)
{
  iface->create_item    = goo_canvas_text_model_create_item;
}
