/*
 * Text.c -- Implementation of Text item.
 *
 * Authors		: Patrick LECOANET
 * Creation date	: Sat Mar 25 13:58:39 1995
 */

/*
 *  Copyright (c) 1993 - 1999 CENA, Patrick Lecoanet --
 *
 * This code 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 code 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 code; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * Some functions in this file are derived from tkCanvText.c and thus
 * copyrighted:
 *
 * Copyright (c) 1991-1994 The Regents of the University of California.
 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
 *
 */

#include <math.h>
#include <ctype.h>
#include <X11/Xatom.h>
#include <string.h>
#include <stdlib.h>

#include "Item.h"
#include "Geo.h"
#include "Draw.h"
#include "Types.h"
#include "WidgetInfo.h"
#include "tkZinc.h"
#include "Image.h"


static const char rcsid[] = "$Imagine: Text.c,v 1.13 1997/05/15 11:35:46 lecoanet Exp $";
static const char compile_id[] = "$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";


/*
 * Bit offset of flags.
 */
#define UNDERLINED	1
#define OVERSTRIKED	2


/*
 **********************************************************************************
 *
 * Specific Text item record
 *
 **********************************************************************************
 */
typedef struct _TextLineInfo 
{
  char		*start;		/* Index of first char in line */
  unsigned short  num_bytes;	/* Number of displayed bytes in line (NOT chars)*/
  unsigned short width;		/* Line width in pixels */
  unsigned short origin_x;	/* X pos for drawing the line */
  unsigned short origin_y;
} TextLineInfoStruct, *TextLineInfo;

typedef struct _TextItemStruct {
  ZnItemStruct	header;

  /* Public data */
  ZnPoint	pos;		/* Position at anchor */
  ZnGradient	*color;
  char		*text;
  ZnImage	fill_pattern;
  Tk_Font	font;
  unsigned short width;
  short		spacing;
  unsigned short flags;
  Tk_Anchor	anchor;
  Tk_Anchor	connection_anchor;
  Tk_Justify	alignment;

  /* Private data */
  unsigned short num_chars;
  unsigned short insert_index;
  ZnList	text_info;
  unsigned short max_width;
  unsigned short height;
  ZnPoint	poly[4];
#ifdef GL
  ZnTexFontInfo	*tfi;
#endif
} TextItemStruct, *TextItem;


static ZnAttrConfig	text_attrs[] = {
  { ZN_CONFIG_ALIGNMENT, "-alignment", NULL,
    Tk_Offset(TextItemStruct, alignment), 0,
    ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
  { ZN_CONFIG_ANCHOR, "-anchor", NULL,
    Tk_Offset(TextItemStruct, anchor), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-color", NULL,
    Tk_Offset(TextItemStruct, color), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-composealpha", NULL,
    Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_ALPHA_BIT,
    ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-composerotation", NULL,
    Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_ROTATION_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BOOL, "-composescale", NULL,
    Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_SCALE_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_ITEM, "-connecteditem", NULL,
    Tk_Offset(TextItemStruct, header.connected_item), 0,
    ZN_COORDS_FLAG|ZN_ITEM_FLAG, False },
  { ZN_CONFIG_ANCHOR, "-connectionanchor", NULL,
    Tk_Offset(TextItemStruct, connection_anchor), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BITMAP, "-fillpattern", NULL,
    Tk_Offset(TextItemStruct, fill_pattern), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_FONT, "-font", NULL,
    Tk_Offset(TextItemStruct, font), 0,
    ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
  { ZN_CONFIG_BOOL, "-overstriked", NULL,
    Tk_Offset(TextItemStruct, flags), OVERSTRIKED, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_POINT, "-position", NULL, Tk_Offset(TextItemStruct, pos), 0,
    ZN_COORDS_FLAG, False},
  { ZN_CONFIG_PRI, "-priority", NULL,
    Tk_Offset(TextItemStruct, header.priority), 0,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG, False },
  { ZN_CONFIG_BOOL, "-sensitive", NULL,
    Tk_Offset(TextItemStruct, header.flags), ZN_SENSITIVE_BIT,
    ZN_REPICK_FLAG, False },
  { ZN_CONFIG_SHORT, "-spacing", NULL,
    Tk_Offset(TextItemStruct, spacing), 0,
    ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
  { ZN_CONFIG_TAG_LIST, "-tags", NULL,
    Tk_Offset(TextItemStruct, header.tags), 0, 0, False },
  { ZN_CONFIG_STRING, "-text", NULL,
    Tk_Offset(TextItemStruct, text), 0,
    ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
  { ZN_CONFIG_BOOL, "-underlined", NULL,
    Tk_Offset(TextItemStruct, flags), UNDERLINED, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-visible", NULL,
    Tk_Offset(TextItemStruct, header.flags), ZN_VISIBLE_BIT,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False },
  { ZN_CONFIG_USHORT, "-width", NULL,
    Tk_Offset(TextItemStruct, width), 0,
    ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False },
  
  { ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False }
};


/*
 **********************************************************************************
 *
 * Init --
 *
 **********************************************************************************
 */
static int
Init(ZnItem		item,
     int		*argc __unused,
     Tcl_Obj *CONST	*args[] __unused)
{
  ZnWInfo	*wi = item->wi;
  TextItem	text = (TextItem) item;

  /*printf("size of a text(header) = %d(%d) %d\n",
    sizeof(TextItemStruct), sizeof(ZnItemStruct), sizeof(TextLineInfoStruct));*/
 
  text->text_info = NULL;
  
  /* Init attributes */
  SET(item->flags, ZN_VISIBLE_BIT);
  SET(item->flags, ZN_SENSITIVE_BIT);
  SET(item->flags, ZN_COMPOSE_ALPHA_BIT);
  CLEAR(item->flags, ZN_COMPOSE_ROTATION_BIT);
  CLEAR(item->flags, ZN_COMPOSE_SCALE_BIT);
  item->priority = 1;

  text->pos.x = text->pos.y = 0.0;
  text->text = NULL;
  text->num_chars = 0;
  text->fill_pattern = ZnUnspecifiedImage;
  text->anchor = TK_ANCHOR_NW;
  text->connection_anchor = TK_ANCHOR_SW;
  text->color = ZnGetGradientByValue(wi->fore_color);
  text->alignment = TK_JUSTIFY_LEFT;
  text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(wi->font));
#ifdef GL
  text->tfi = NULL;
#endif
  text->width = 0;
  text->spacing = 0;
  text->insert_index = 0;
  CLEAR(text->flags, UNDERLINED);
  CLEAR(text->flags, OVERSTRIKED);

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Clone --
 *
 **********************************************************************************
 */
static void
Clone(ZnItem	item)
{
  TextItem	text = (TextItem) item;
  ZnWInfo	*wi = item->wi;
  char		*str;

  if (text->text) {
    str = ZnMalloc((strlen(text->text) + 1) * sizeof(char));
    strcpy(str, text->text);
    text->text = str;
  }
  if (text->fill_pattern != ZnUnspecifiedImage) {
    text->fill_pattern = ZnGetImageByValue(text->fill_pattern, NULL, NULL);
  }
  text->color = ZnGetGradientByValue(text->color);
  text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(text->font));
#ifdef GL
  text->tfi = NULL;
#endif

  /*
   * We always need to invalidate, either because the model
   * has not done its layout (text_info == NULL) or because
   * we must unshare the pointers to the text that are in
   * text_info.
   */
  text->text_info = NULL;
  ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG);
}


/*
 **********************************************************************************
 *
 * Destroy --
 *
 **********************************************************************************
 */
static void
Destroy(ZnItem	item)
{
  TextItem	text = (TextItem) item;

  if (text->text) {
    ZnFree(text->text);
  }
  if (text->fill_pattern != ZnUnspecifiedImage) {
    ZnFreeImage(text->fill_pattern, NULL, NULL);
    text->fill_pattern = ZnUnspecifiedImage;
  }
  ZnFreeGradient(text->color);
  Tk_FreeFont(text->font);
#ifdef GL
  if (text->tfi) {
    ZnFreeTexFont(text->tfi);
  }
#endif
  
  if (text->text_info) {
    ZnListFree(text->text_info);
  }
}


/*
 **********************************************************************************
 *
 * Configure --
 *
 **********************************************************************************
 */
static int
Configure(ZnItem	item,
	  int		argc,
	  Tcl_Obj *CONST argv[],
	  int		*flags)
{
  TextItem	text = (TextItem) item;
  ZnItem	old_connected = item->connected_item;
  unsigned int	num_chars;
#ifdef GL
  Tk_Font	old_font = text->font;
#endif

  if (ZnConfigureAttributes(item->wi, item, item, text_attrs,
			    argc, argv, flags) == TCL_ERROR) {
    return TCL_ERROR;
  }

#ifdef GL
  if (old_font != text->font) {
    if (text->tfi) {
      ZnFreeTexFont(text->tfi);
      text->tfi = NULL;
    }
  }
#endif
  num_chars = 0;
  if (text->text) {
    num_chars = Tcl_NumUtfChars(text->text, (int) strlen(text->text));
  }
  if (text->num_chars != num_chars) {
    ZnTextInfo *ti = &item->wi->text_info;
    /*
     * The text has changed, update the selection and
     * insertion pos to keep them valid.
     */
    if (item == ti->sel_item) {
      if (ti->sel_last > (int) num_chars) {
	ti->sel_last = num_chars;
      }
      if (ti->sel_first >= ti->sel_last) {
	ti->sel_item = ZN_NO_ITEM;
	ti->sel_field = ZN_NO_PART;
      }
      if ((ti->anchor_item == item) && (ti->sel_anchor > (int) num_chars)) {
	ti->sel_anchor = num_chars;
      }
    }
    if (text->insert_index > num_chars) {
      text->insert_index = num_chars;
    }
    text->num_chars = num_chars;
  }

  if (ISSET(*flags, ZN_ITEM_FLAG)) {
    /*
     * If the new connected item is not appropriate back up
     * to the old one.
     */
    if ((item->connected_item == ZN_NO_ITEM) ||
	(item->connected_item->class->has_anchors &&
	 (item->parent == item->connected_item->parent))) {
      ZnITEM.UpdateItemDependency(item, old_connected);
    }
    else {
      item->connected_item = old_connected;
    }
  }
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Query --
 *
 **********************************************************************************
 */
static int
Query(ZnItem		item,
      int		argc __unused,
      Tcl_Obj *CONST	argv[])
{
  if (ZnQueryAttribute(item->wi, item, text_attrs, argv[0]) == TCL_ERROR) {
    return TCL_ERROR;
  }  

  return TCL_OK;
}


/*
 * Compute the transformation to be used and the origin
 * of the text (upper left point in item coordinates).
 */
static ZnTransfo *
ComputeTransfoAndOrigin(ZnItem	  item,
			ZnPoint   *origin)
{
  TextItem text = (TextItem) item;

  /*
   * The connected item support anchors, this is checked by configure.
   */
  if (item->connected_item != ZN_NO_ITEM) {
    ZnTransfo inv;

    item->connected_item->class->GetAnchor(item->connected_item,
					   text->connection_anchor,
					   origin);

    /* GetAnchor return a position in device coordinates not in
     * the item coordinate space. To compute the text origin
     * (upper left corner), we must apply the inverse transform
     * to the ref point before calling anchor2origin.
     */
    ZnTransfoInvert(item->transfo, &inv);
    ZnTransformPoint(&inv, origin, origin);
    ZnAnchor2Origin(origin, (ZnReal) text->max_width,
		    (ZnReal) text->height, text->anchor, origin);
    origin->x = ZnNearestInt(origin->x);
    origin->y = ZnNearestInt(origin->y);
    /*
     * The relevant transform in case of an attachment is the item
     * transform alone. This is case of local coordinate space where
     * only the translation is a function of the whole transform
     * stack, scale and rotation are reset.
     */
    return item->transfo;
  }
  else {
    ZnPoint p;
    p.x = p.y = 0;
    ZnAnchor2Origin(&p, (ZnReal) text->max_width,
		    (ZnReal) text->height, text->anchor, origin);
    origin->x = ZnNearestInt(origin->x);
    origin->y = ZnNearestInt(origin->y);

    return item->wi->current_transfo;
  }
}


/*
 * Compute the selection and the cursor geometry.
 */
void
ComputeCursor(ZnItem	   item,
	      int	   *cursor_line,
	      unsigned int *cursor_offset)
{
  TextItem	text = (TextItem) item;
  ZnWInfo	*wi = item->wi;
  ZnTextInfo	*ti = &wi->text_info;
  TextLineInfo	lines, lines_ptr;
  unsigned int  i, line_index, insert_index, num_lines;

  num_lines = ZnListSize(text->text_info);
  if (num_lines == 0) {
    *cursor_line = 0;
  }

  lines = ZnListArray(text->text_info);
  if ((wi->focus_item == item) && ISSET(wi->flags, ZN_GOT_FOCUS) && ti->cursor_on) {
    insert_index = Tcl_UtfAtIndex(text->text, (int) text->insert_index)-text->text;
    for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
      /*
       * Mark the line with the cursor and compute its
       * position along the X axis.
       */
      line_index = lines_ptr->start - text->text;
      if ((insert_index >= line_index) &&
	  (insert_index <= line_index + lines_ptr->num_bytes)) {
	*cursor_line = i;
	*cursor_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start,
				      insert_index - line_index);
      }
    }
  }
  
}

static void
ComputeSelection(ZnItem		item,
		 int		*sel_first_line,
		 int		*sel_last_line,
		 unsigned int	*sel_start_offset,
		 unsigned int	*sel_stop_offset)
{  
  TextItem	text = (TextItem) item;
  ZnWInfo	*wi = item->wi;
  ZnTextInfo	*ti = &wi->text_info;
  TextLineInfo	lines_ptr, lines;
  int		i, num_lines, byte_index;
  unsigned int  line_index;
  unsigned int  sel_first, sel_last;

  num_lines = ZnListSize(text->text_info);

  if ((ti->sel_item != item) || !num_lines) {
    return;
  }

  lines = ZnListArray(text->text_info);

  sel_first = Tcl_UtfAtIndex(text->text, ti->sel_first)-text->text;
  sel_last = Tcl_UtfAtIndex(text->text, ti->sel_last+1)-text->text;
  for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
    /*
     * Compute the selection first and last line as well as
     * the positions along the X axis.
     */
    line_index = lines_ptr->start - text->text;
    if ((sel_last >= line_index) &&
	(sel_first <= (line_index + lines_ptr->num_bytes))) {
      if (*sel_first_line < 0) {
	byte_index = sel_first - line_index;
	if (byte_index <= 0) {
	  *sel_first_line = i;
	  *sel_start_offset = 0;
	  /*printf("sel_start_offset 1 : %d\n", *sel_start_offset);*/
	}
	else if (byte_index <= lines_ptr->num_bytes) {
	  *sel_first_line = i;
	  *sel_start_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start,
					   byte_index);
	  /*printf("sel_start_offset 2 : %d\n", *sel_start_offset);*/
	}
      }
      byte_index = ti->sel_last+1 - line_index;
      *sel_last_line = i;
      if (byte_index == lines_ptr->num_bytes+1)
	*sel_stop_offset = lines_ptr->width;
      else if (byte_index <= lines_ptr->num_bytes)
	*sel_stop_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start,
					byte_index);
    }
  }
}


/*
 **********************************************************************************
 *
 * ComputeCoordinates --
 *
 **********************************************************************************
 */
static void
ComputeCoordinates(ZnItem	item,
		   ZnBool	force __unused)
{
  ZnWInfo	*wi = item->wi;
  TextItem	text = (TextItem) item;
  TextLineInfo	infos;
  Tk_FontMetrics fm;
  ZnTransfo	*transfo;
  int		i, num_lines, cur_dy, font_height;
  
  ZnResetBBox(&item->item_bounding_box);
  
  Tk_GetFontMetrics(text->font, &fm);
  font_height = fm.ascent+fm.descent;
  
  /*
   * The layout need not be done each time the item is moved, scaled
   * or rotated.
   */
  if (ISSET(item->inv_flags, ZN_LAYOUT_FLAG)) {
    char *scan;
    int	 wrap, prev_num_lines;

    text->max_width = 0;
    if (text->text_info != NULL) {
      prev_num_lines = ZnListSize(text->text_info);
      ZnListEmpty(text->text_info);
    }
    else {
      prev_num_lines = 0;
      text->text_info = ZnListNew(1, sizeof(TextLineInfoStruct));
    }
    
    if (text->width > 0) {
      wrap = text->width;
    }
    else {
      wrap = 100000;
    }
    
    if ((scan = text->text) != NULL) {
      TextLineInfoStruct info;

      while (*scan) {
	char	*special;
	int	num, w;

	/*
	 * Limit the excursion of Tk_MeasureChars to the end
	 * of the line. Do not include \n in the measure done.
	 */
	num = strcspn(scan, "\r\n");
	special = scan + num;
	info.num_bytes = Tk_MeasureChars(text->font, scan, num, wrap,
					 TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &w);
	
	info.width = w;
	info.start = scan;
	text->max_width = MAX(info.width, text->max_width);
	
	scan += info.num_bytes;

	/*
	 * Skip the newline line character.
	 */
	if ((*scan == '\r') || (*scan == '\n')) {
	  scan++;
	}
	else {
	  /*
	   * Skip white spaces occuring after an
	   * automatic line break.
	   */
	  while (*scan == ' ') {
	    scan++;
	  }
	}
	
	ZnListAdd(text->text_info, &info, ZnListTail);
	/*printf("adding a text info : %s, num_bytes :  %d, width : %d\n",
	  info.start, info.num_bytes, info.width);*/
      }
      if (*text->text && ((scan[-1] == '\r') || (scan[-1] == '\n'))) {
	/* Build a text info even for an empty line
	 * at the end of text or for an empty text.
	 * It is needed to enable selection and cursor
	 * insertion to behave correctly.
	 */
	info.num_bytes = 0;
	info.width = 0;
	info.start = scan;
	ZnListAdd(text->text_info, &info, ZnListTail);
      }
    }
  
    /*
     * Compute x & y positions for all lines in text_info. The coordinates are
     * in text natural units NOT transformed units.
     */
    cur_dy = fm.ascent;
    num_lines = ZnListSize(text->text_info);
    infos = (TextLineInfo) ZnListArray(text->text_info);
    
    for (i = 0; i < num_lines; i++) {
      switch (text->alignment) {
      case TK_JUSTIFY_LEFT:
	infos[i].origin_x = 0;
	break;
      case TK_JUSTIFY_CENTER:
	infos[i].origin_x = (text->max_width + 1 - infos[i].width)/2;
	break;
      case TK_JUSTIFY_RIGHT:
	infos[i].origin_x = text->max_width + 1 - infos[i].width;
	break;
      }
      infos[i].origin_y = cur_dy;
      cur_dy += font_height + text->spacing;
      /*printf("fixing line %d x : %d, y : %d\n", i, infos[i].origin_x,
	infos[i].origin_y);*/
    }
  } /* ISSET(item->inv_flags, INV_TEXT_LAYOUT) */
    
  text->height = font_height;
  if (text->text_info) {
    unsigned int h, cursor_offset;
    int		 cursor_line;
    ZnPoint	 origin, box[4];

    num_lines = ZnListSize(text->text_info);
    infos = ZnListArray(text->text_info);
    h = num_lines * font_height + (num_lines-1) * text->spacing;
    text->height = MAX(text->height, h);

    transfo = ComputeTransfoAndOrigin(item, &origin);
    
    text->poly[0].x = origin.x;
    text->poly[0].y = origin.y;
    text->poly[3].x = text->poly[0].x + text->max_width;
    text->poly[3].y = text->poly[0].y + text->height;
    text->poly[1].x = text->poly[0].x;
    text->poly[1].y = text->poly[3].y;
    text->poly[2].x = text->poly[3].x;
    text->poly[2].y = text->poly[0].y;
    ZnTransformPoints(transfo, text->poly, text->poly, 4);       

    /*
     * Add to the bounding box.
     */
    ZnAddPointsToBBox(&item->item_bounding_box, text->poly, 4);

    /*
     * Add the cursor shape to the bbox.
     */
    cursor_line = -1;
    ComputeCursor(item, &cursor_line, &cursor_offset);
    if (cursor_line >= 0) {
      box[0].x = origin.x + infos[cursor_line].origin_x + cursor_offset -
	wi->text_info.insert_width/2;
      box[0].y = origin.y + infos[cursor_line].origin_y - fm.ascent + 1;
      box[2].x = box[0].x + wi->text_info.insert_width;
      box[2].y = box[0].y + font_height - 1;
      box[1].x = box[2].x;
      box[1].y = box[0].y;
      box[3].x = box[0].x;
      box[3].y = box[2].y;
      ZnTransformPoints(transfo, box, box, 4);
      ZnAddPointsToBBox(&item->item_bounding_box, box, 4);
    }
  }

  /*printf("bbox origin: %g %g corner %g %g\n",
	 item->item_bounding_box.orig.x, item->item_bounding_box.orig.y,
	 item->item_bounding_box.corner.x, item->item_bounding_box.corner.y);*/
  /*
   * Update connected items.
   */
  SET(item->flags, ZN_UPDATE_DEPENDENT_BIT);
}


/*
 **********************************************************************************
 *
 * ToArea --
 *	Tell if the object is entirely outside (-1),
 *	entirely inside (1) or in between (0).
 *
 **********************************************************************************
 */
static int
ToArea(ZnItem	item,
       ZnToArea	ta)
{
  TextItem	text = (TextItem) item;
  int		inside = -1;
  ZnBool	first_done = False;
  int		num_lines, i;
  TextLineInfo	lines, lines_ptr;
  Tk_FontMetrics fm;
  int		font_height;
  ZnBBox	line_bbox, *area = ta->area;
  ZnPoint	box[4], p, origin;
  ZnTransfo	inv, *transfo;

  if (!text->text_info || !text->text) {
    return -1;
  }
  
  transfo = ComputeTransfoAndOrigin(item, &origin);
  box[0] = area->orig;
  box[2] = area->corner;
  box[1].x = box[2].x;
  box[1].y = box[0].y;
  box[3].x = box[0].x;
  box[3].y = box[2].y;
  ZnTransfoInvert(transfo, &inv);
  ZnTransformPoints(&inv, box, box, 4);

  lines = (TextLineInfo) ZnListArray(text->text_info);
  num_lines = ZnListSize(text->text_info);
  Tk_GetFontMetrics(text->font, &fm);
  font_height = fm.descent + fm.ascent;
  if (text->spacing > 0) {
    font_height += text->spacing;
  }

  /*printf("text %d, num lines=%d\n", item->id, num_lines);*/
  for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
    ZnResetBBox(&line_bbox);
    p.x = origin.x + lines_ptr->origin_x;
    p.y = origin.y + lines_ptr->origin_y - fm.ascent;
    ZnAddPointToBBox(&line_bbox, p.x, p.y);
    ZnAddPointToBBox(&line_bbox, p.x + lines_ptr->width, p.y + font_height);
    if (!first_done) {
      first_done = True;
      inside = ZnPolygonInBBox(box, 4, &line_bbox, NULL);
      if (inside == 0) {
	return 0;
      }
    }
    else {
      if (ZnPolygonInBBox(box, 4, &line_bbox, NULL) == 0) {
	return 0;
      }
    }
  }

  return inside;
}


/*
 **********************************************************************************
 *
 * Draw --
 *
 **********************************************************************************
 */
static void
Draw(ZnItem	item)
{
  ZnWInfo	*wi = item->wi;
  TextItem	text = (TextItem) item;
  XGCValues  	values;
  ZnPoint	pos, box[4], origin;
  ZnTransfo	*transfo;
  Drawable	drw;
  GC		gc;
  XImage	*src_im, *dest_im=NULL;
  unsigned int  dest_im_width=0, dest_im_height=0;
  unsigned int	gc_mask = 0;
  Tk_FontMetrics fm;
  unsigned int	font_height;  
  int		num_lines, i;
  TextLineInfo	lines, lines_ptr;
  ZnTextInfo	*ti = &wi->text_info;
  unsigned int	underline_thickness, underline_pos=0, overstrike_pos=0;
  int		sel_first_line=-1, sel_last_line=-1, cursor_line=-1;
  unsigned int	sel_start_offset=0, sel_stop_offset=0, cursor_offset=0;

  if (!text->text_info/* || !text->text*/) {
    return;
  }

  lines = (TextLineInfo) ZnListArray(text->text_info);
  num_lines = ZnListSize(text->text_info);
  Tk_GetFontMetrics(text->font, &fm);
  font_height = fm.ascent+fm.descent;

  transfo = ComputeTransfoAndOrigin(item, &origin);

  /*
   * Compute the selection and the cursor geometry.
   */
  ComputeCursor(item, &cursor_line, &cursor_offset);
  ComputeSelection(item, &sel_first_line, &sel_last_line,
		   &sel_start_offset, &sel_stop_offset);


  ZnTransformPoint(transfo, &origin, &pos);

  /*printf("sel 1st : %d offset : %d, sel last : %d offset : %d\n",
    sel_first_line, sel_start_offset, sel_last_line, sel_stop_offset);*/
  /*
   * Setup the gc for the selection and fill the selection.
   */
  if ((ti->sel_item == item) && (sel_first_line >= 0)) {
    XPoint xp[4];

    values.foreground = ZnGetGradientPixel(ti->sel_color, 0.0);
    values.fill_style = FillSolid;
    XChangeGC(wi->dpy, wi->gc, GCFillStyle | GCForeground, &values);
    
    if (sel_first_line == sel_last_line) {
      box[0].x = origin.x + 
	lines[sel_first_line].origin_x + sel_start_offset;
      box[0].y = origin.y +
	lines[sel_first_line].origin_y - fm.ascent;
      box[2].x = box[0].x + sel_stop_offset - sel_start_offset;
      box[2].y = box[0].y + font_height;
      box[1].x = box[2].x;
      box[1].y = box[0].y;
      box[3].x = box[0].x;
      box[3].y = box[2].y;
      ZnTransformPoints(transfo, box, box, 4);
      for (i = 0; i < 4; i++) {
	xp[i].x = box[i].x;
	xp[i].y = box[i].y;
      }
      XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
    }
    else {
      box[0].x = origin.x + 
	lines[sel_first_line].origin_x + sel_start_offset;
      box[0].y = origin.y +
	lines[sel_first_line].origin_y - fm.ascent;
      box[2].x = box[0].x +
	text->max_width-lines[sel_first_line].origin_x-sel_start_offset;
      box[2].y = box[0].y + font_height;
      box[1].x = box[2].x;
      box[1].y = box[0].y;
      box[3].x = box[0].x;
      box[3].y = box[2].y;
      ZnTransformPoints(transfo, box, box, 4);
      for (i = 0; i < 4; i++) {
	xp[i].x = box[i].x;
	xp[i].y = box[i].y;
      }
      XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
      for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1];
	   i < sel_last_line; i++, lines_ptr++) {
	box[0].x = origin.x;
	box[0].y = origin.y + lines_ptr->origin_y - fm.ascent;
	box[2].x = box[0].x + text->max_width;
	box[2].y = box[0].y + font_height;
	box[1].x = box[2].x;
	box[1].y = box[0].y;
	box[3].x = box[0].x;
	box[3].y = box[2].y;
	ZnTransformPoints(transfo, box, box, 4);
	for (i = 0; i < 4; i++) {
	  xp[i].x = box[i].x;
	  xp[i].y = box[i].y;
	}
	XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
      }
      box[0].x = origin.x;
      box[0].y = origin.y + lines[sel_last_line].origin_y - fm.ascent;
      box[2].x = box[0].x + lines[sel_last_line].origin_x + sel_stop_offset;
      box[2].y = box[0].y + font_height;
      box[1].x = box[2].x;
      box[1].y = box[0].y;
      box[3].x = box[0].x;
      box[3].y = box[2].y;
      ZnTransformPoints(transfo, box, box, 4);
      for (i = 0; i < 4; i++) {
	xp[i].x = box[i].x;
	xp[i].y = box[i].y;
      }
      XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
    }
  }

  /*printf("cursor line : %d, cursor offset : %d\n", cursor_line, cursor_offset);*/
  /*
   * Setup the gc for the cursor and draw it.
   */
  if (cursor_line >= 0 &&
      (wi->focus_item == item) && ti->cursor_on) {
    values.fill_style = FillSolid;
    values.line_width = ti->insert_width;
    values.foreground = ZnGetGradientPixel(ti->insert_color, 0.0);
    XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle|GCLineWidth, &values);

    box[0].x = origin.x + lines[cursor_line].origin_x + cursor_offset;
    box[0].y = origin.y + lines[cursor_line].origin_y - fm.ascent + 1;
    box[1].x = box[0].x;
    box[1].y = box[0].y + font_height - 1;
    ZnTransformPoints(transfo, box, box, 2);
    XDrawLine(wi->dpy, wi->draw_buffer, wi->gc,
	      box[0].x, box[0].y, box[1].x, box[1].y);
  }


  /*
   * If the current transform is a pure translation, it is
   * possible to optimize by directly drawing to the X back
   * buffer. Else, we draw in a temporary buffer, get
   * its content as an image, transform the image into another
   * one and use this last image as a mask to draw in the X
   * back buffer.
   */
  if (ZnTransfoIsTranslation(transfo)) {
    drw = wi->draw_buffer;

    gc = wi->gc;
    values.foreground = ZnGetGradientPixel(text->color, 0.0);    
  }
  else {
    dest_im_width = (item->item_bounding_box.corner.x -
		     item->item_bounding_box.orig.x);
    dest_im_height = (item->item_bounding_box.corner.y -
		      item->item_bounding_box.orig.y);

    drw = Tk_GetPixmap(wi->dpy, wi->draw_buffer,
		       MAX(dest_im_width, text->max_width),
		       MAX(dest_im_height, text->height), 1);
    gc = XCreateGC(wi->dpy, drw, 0, NULL);
    XSetForeground(wi->dpy, gc, 0);
    XFillRectangle(wi->dpy, drw, gc, 0, 0,
		   MAX(dest_im_width, text->max_width),
		   MAX(dest_im_height, text->height));
    dest_im = XCreateImage(wi->dpy, Tk_Visual(wi->win), 1,
			   XYPixmap, 0, NULL, dest_im_width, dest_im_height,
			   8, 0);
    dest_im->data = ZnMalloc(dest_im->bytes_per_line * dest_im->height);
    memset(dest_im->data, 0, dest_im->bytes_per_line * dest_im->height);

    values.foreground = 1;
 
    pos.x = 0;
    pos.y = 0;
  }

  /*
   * Setup the gc to render the text and draw it.
   */
  values.font = Tk_FontId(text->font);
  gc_mask = GCFont | GCForeground;
  if (text->fill_pattern != ZnUnspecifiedImage) {
    values.fill_style = FillStippled;
    values.stipple = ZnImagePixmap(text->fill_pattern, wi->win);
    gc_mask |= GCFillStyle | GCStipple;
  }
  else {
    values.fill_style = FillSolid;
    gc_mask |= GCFillStyle;
  }
  if (ISSET(text->flags, UNDERLINED) || ISSET(text->flags, OVERSTRIKED)) {
    /*
     * These 3 values should be fetched from the font.
     * Currently I don't know how without diving into
     * Tk internals.
     */
    underline_thickness = 2;
    underline_pos = fm.descent/2;
    overstrike_pos = fm.ascent*3/10;
    values.line_style = LineSolid;
    values.line_width = underline_thickness;
    gc_mask |= GCLineStyle | GCLineWidth;
  }
  XChangeGC(wi->dpy, gc, gc_mask, &values);
  for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
    int	tmp_x, tmp_y;
    
    tmp_x = (int)(pos.x + lines_ptr->origin_x);
    tmp_y = (int)(pos.y + lines_ptr->origin_y);
    Tk_DrawChars(wi->dpy, drw, gc,
		 text->font, (char *) lines_ptr->start,
		 (int) lines_ptr->num_bytes, tmp_x, tmp_y);
    if (ISSET(text->flags, UNDERLINED)) {
      int y_under = tmp_y + underline_pos;
      
      XDrawLine(wi->dpy, drw, gc,
		tmp_x, y_under, tmp_x + (int) lines_ptr->width, y_under);
    }
    if (ISSET(text->flags, OVERSTRIKED)) {
      int y_over = tmp_y-overstrike_pos;
      
      XDrawLine(wi->dpy, drw, gc,
		tmp_x, y_over, tmp_x + (int) lines_ptr->width, y_over);
    }
  }

  if (dest_im != NULL) {
    src_im = XGetImage(wi->dpy, drw, 0, 0, text->max_width, text->height,
		       1, XYPixmap);

    box[0].x = origin.x;
    box[0].y = origin.y;
    box[3].x = box[0].x + text->max_width;
    box[3].y = box[0].y + text->height;
    box[1].x = box[0].x;
    box[1].y = box[3].y;
    box[2].x = box[3].x;
    box[2].y = box[0].y;
    ZnTransformPoints(transfo, box, box, 4);
    for (i = 0; i < 4; i++) {
      box[i].x -= item->item_bounding_box.orig.x;
      box[i].y -= item->item_bounding_box.orig.y;
      box[i].x = ZnNearestInt(box[i].x);
      box[i].y = ZnNearestInt(box[i].y);
    }

    ZnMapImage(src_im, dest_im, box);

    XPutImage(wi->dpy, drw, gc, dest_im,
	      0, 0, 0, 0, dest_im_width, dest_im_height);

    values.foreground = ZnGetGradientPixel(text->color, 0.0);
    values.stipple = drw;
    values.ts_x_origin = item->item_bounding_box.orig.x;
    values.ts_y_origin = item->item_bounding_box.orig.y;
    values.fill_style = FillStippled;
    XChangeGC(wi->dpy, wi->gc,
	      GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground,
	      &values);
    XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc,
		   item->item_bounding_box.orig.x,
		   item->item_bounding_box.orig.y,
		   dest_im_width, dest_im_height);

    XFreeGC(wi->dpy, gc);
    Tk_FreePixmap(wi->dpy, drw);
    XDestroyImage(src_im);
    XDestroyImage(dest_im);
  }
}
  

/*
**********************************************************************************
 *
 * Render --
 *
 **********************************************************************************
 */
#ifdef GL
static void
Render(ZnItem	item)
{
  ZnWInfo	*wi = item->wi;
  TextItem	text = (TextItem) item;
  TextLineInfo	lines, lines_ptr;
  ZnTextInfo	*ti = &wi->text_info;
  ZnPoint	o, c, origin;
  ZnTransfo	*transfo;
  GLdouble	m[16];
  int		i, num_lines;
  XColor	*color;
  unsigned short alpha;
  Tk_FontMetrics fm;
  int		font_height;  
  int		underline_thickness, underline_pos=0, overstrike_pos=0;
  int		sel_first_line=-1, sel_last_line=-1, cursor_line=-1;
  int		sel_start_offset=0, sel_stop_offset=0, cursor_offset=0;
#ifndef PTK_800
  Tcl_Encoding	enc;
#endif

  if (!text->text_info) {
    return;
  }

  if (!text->tfi) {
    if (! (text->tfi = ZnGetTexFont(wi, text->font))) {
      return;
    }
  }
#ifndef PTK_800
  enc = ZnTexFontEncoding(text->tfi);
#endif

#ifdef GL_LIST
  if (!item->gl_list) {
    item->gl_list = glGenLists(1);
    glNewList(item->gl_list, GL_COMPILE);
#endif
    lines = (TextLineInfo) ZnListArray(text->text_info);
    num_lines = ZnListSize(text->text_info);
    Tk_GetFontMetrics(text->font, &fm);
    font_height = fm.ascent+fm.descent;
    
    /*
     * These 3 values should be fetched from the font.
     * Currently I don't know how without diving into
     * Tk internals.
     */
    underline_thickness = 2;
    underline_pos = fm.descent/2;
    overstrike_pos = fm.ascent*3/10;
    
    transfo = ComputeTransfoAndOrigin(item, &origin);
  
    /*
     * Compute the selection and the cursor geometry.
     */
    ComputeCursor(item, &cursor_line, &cursor_offset);
    ComputeSelection(item, &sel_first_line, &sel_last_line,
		     &sel_start_offset, &sel_stop_offset);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glPushMatrix();
    memset(m, 0, sizeof(m));
    m[0] = m[5] = m[15] = 1.0;
    if (transfo) {
      m[0] = transfo->_[0][0]; m[1] = transfo->_[0][1];
      m[4] = transfo->_[1][0]; m[5] = transfo->_[1][1];
      m[12] = transfo->_[2][0]; m[13] = transfo->_[2][1];
    }
    glLoadMatrixd(m);
    glTranslated(origin.x, origin.y, 0.0);
    glPushMatrix();    

    /*
     * Render the selection.
     */
    if ((ti->sel_item == item) && (sel_first_line >= 0)) {
      color = ZnGetGradientColor(ti->sel_color, 0.0, &alpha);
      alpha = ZnComposeAlpha(alpha, wi->alpha);
      glColor4us(color->red, color->green, color->blue, alpha);
      o.x = lines[sel_first_line].origin_x + sel_start_offset;
      o.y = lines[sel_first_line].origin_y - fm.ascent;
      glBegin(GL_QUADS);
      if (sel_first_line == sel_last_line) {
	c.x = o.x + sel_stop_offset - sel_start_offset;
	c.y = o.y + font_height;
	glVertex2d(o.x, o.y);
	glVertex2d(o.x, c.y);
	glVertex2d(c.x, c.y);
	glVertex2d(c.x, o.y);
      }
      else {
	c.x = o.x + (text->max_width - 
		     lines[sel_first_line].origin_x - sel_start_offset);
	c.y = o.y + font_height;
	glVertex2d(o.x, o.y);
	glVertex2d(o.x, c.y);
	glVertex2d(c.x, c.y);
	glVertex2d(c.x, o.y);
	for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1];
	     i < sel_last_line; i++, lines_ptr++) {
	  o.x = 0;
	  o.y = lines_ptr->origin_y - fm.ascent;
	  c.x = o.x + text->max_width;
	  c.y = o.y + font_height;
	  glVertex2d(o.x, o.y);
	  glVertex2d(o.x, c.y);
	  glVertex2d(c.x, c.y);
	  glVertex2d(c.x, o.y);
	}
	o.x = 0;
	o.y = lines[sel_last_line].origin_y - fm.ascent;
	c.x = o.x + lines[sel_last_line].origin_x + sel_stop_offset;
	c.y = o.y + font_height;
	glVertex2d(o.x, o.y);
	glVertex2d(o.x, c.y);
	glVertex2d(c.x, c.y);
	glVertex2d(c.x, o.y);
      }
      glEnd();
    }

    /*
     * Render the cursor.
     */
    if ((cursor_line >= 0) &&
	(wi->focus_item == item) && ti->cursor_on) {
      color = ZnGetGradientColor(ti->insert_color, 0.0, &alpha);
      alpha = ZnComposeAlpha(alpha, wi->alpha);
      glColor4us(color->red, color->green, color->blue, alpha);
      glLineWidth((GLfloat) ti->insert_width);
      
      o.x = lines[cursor_line].origin_x + cursor_offset;
      o.y = lines[cursor_line].origin_y - fm.ascent + 1;
      c.x = o.x;
      c.y = o.y + font_height - 1;

      glBegin(GL_LINES);
      glVertex2d(o.x, o.y);
      glVertex2d(c.x, c.y);
      glEnd();
    }

    /*
     * Render the text.
     */
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glBindTexture(GL_TEXTURE_2D, ZnTexFontTex(text->tfi));
    color = ZnGetGradientColor(text->color, 0.0, &alpha);
    alpha = ZnComposeAlpha(alpha, wi->alpha);
    glColor4us(color->red, color->green, color->blue, alpha);
    
    for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
      glTranslated(lines_ptr->origin_x, lines_ptr->origin_y, 0.0);

      if (ISSET(text->flags, UNDERLINED) || ISSET(text->flags, OVERSTRIKED)) {
	glLineWidth((GLfloat) underline_thickness);
	glDisable(GL_TEXTURE_2D);
	if (ISSET(text->flags, UNDERLINED)) {
	  glBegin(GL_LINES);
	  glVertex2d(0, underline_pos);
	  glVertex2d(lines_ptr->width, underline_pos);
	  glEnd();
	}
	if (ISSET(text->flags, OVERSTRIKED)) {
	  glBegin(GL_LINES);
	  glVertex2d(0, -overstrike_pos);
	  glVertex2d(lines_ptr->width, -overstrike_pos);
	  glEnd();
	}
	glEnable(GL_TEXTURE_2D);
      }

#ifndef PTK_800
      /*
       * Temporary ack to fix utf8 display.
       * the malloc should be done once for
       * each rendering of the item and not
       * for each line. The max line size should
       * be stored by CC.
       */
      {
	char	*buf;
	int	result, written;

	buf = ZnMalloc(lines_ptr->num_bytes+1);
	if ((result = Tcl_UtfToExternal(wi->interp, enc,
				       lines_ptr->start, lines_ptr->num_bytes,
				       TCL_ENCODING_START|TCL_ENCODING_END,
				       NULL, buf, lines_ptr->num_bytes+1, NULL,
				       &written, NULL)) != TCL_OK) {
	  printf("Failed to convert the string: %d\n", result);
	}
	ZnRenderString(text->tfi, buf, written);
	ZnFree(buf);
      }
#else
      ZnRenderString(text->tfi, lines_ptr->start, lines_ptr->num_bytes);
#endif
      glPopMatrix();
      glPushMatrix();
    }

    glPopMatrix();
    glPopMatrix();
      
    glDisable(GL_TEXTURE_2D);
#ifdef GL_LIST    
    glEndList();
  }
  
  glCallList(item->gl_list);
#endif
}
#else
static void
Render(ZnItem	item __unused)
{
}
#endif


/*
 **********************************************************************************
 *
 * IsSensitive --
 *
 **********************************************************************************
 */
static ZnBool
IsSensitive(ZnItem	item,
	    int		item_part __unused)
{
  return (ISSET(item->flags, ZN_SENSITIVE_BIT) &&
	  item->parent->class->IsSensitive(item->parent, ZN_NO_PART));
}


/*
 **********************************************************************************
 *
 * Pick --
 *
 **********************************************************************************
 */
static double
Pick(ZnItem	item,
     ZnPick	ps)
{
  TextItem	text = (TextItem) item;
  double	dist = 1.0e40, new_dist;
  int		num_lines, i;
  TextLineInfo	lines, lines_ptr;
  Tk_FontMetrics fm;
  int		font_height;
  ZnPoint	box[4], origin, *p = ps->point;
  ZnTransfo	*transfo;

  if (!text->text_info || !text->text) {
    return dist;
  }
  
  transfo = ComputeTransfoAndOrigin(item, &origin);
  
  lines = ZnListArray(text->text_info);
  num_lines = ZnListSize(text->text_info);
  Tk_GetFontMetrics(text->font, &fm);
  font_height = fm.descent + fm.ascent;
  if (text->spacing > 0) {
    font_height += text->spacing;
  }  

  for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) {
    box[0].x = origin.x + lines_ptr->origin_x;
    box[0].y = origin.y + lines_ptr->origin_y - fm.ascent;
    box[2].x = box[0].x + lines_ptr->width;
    box[2].y = box[0].y + font_height;
    box[1].x = box[2].x;
    box[1].y = box[0].y;
    box[3].x = box[0].x;
    box[3].y = box[2].y;
    ZnTransformPoints(transfo, box, box, 4);
    new_dist = ZnPolygonToPointDist(box, 4, p);
    dist = MIN(dist, new_dist);
    if (dist <= 0.0) {
      dist = 0.0;
      break;
    }
  }

  return dist;
}


/*
 **********************************************************************************
 *
 * PostScript --
 *
 **********************************************************************************
 */
static void
PostScript(ZnItem	item __unused,
	   ZnBool	prepass __unused)
{
}


/*
 **********************************************************************************
 *
 * GetAnchor --
 *
 **********************************************************************************
 */
static void
GetAnchor(ZnItem	item,
	  Tk_Anchor	anchor,
	  ZnPoint	*p)
{
  TextItem	text = (TextItem) item;

  if (text->num_chars != 0) {
    ZnRectOrigin2Anchor(text->poly, anchor, p);
  }
  else {
    *p = *text->poly;
  }
}


/*
 **********************************************************************************
 *
 * GetClipVertices --
 *	Get the clipping shape.
 *	Never ever call ZnTriFree on the tristrip returned by GetClipVertices.
 *
 **********************************************************************************
 */
static ZnBool
GetClipVertices(ZnItem		item,
		ZnTriStrip	*tristrip)
{
  TextItem	text = (TextItem) item;

  ZnTriStrip1(tristrip, text->poly, 4, False);

  return False;
}


/*
 **********************************************************************************
 *
 * Coords --
 *	Return or edit the item origin. This doesn't take care of
 *	the possible attachment. The change will be effective at the
 *	end of the attachment.
 *
 **********************************************************************************
 */
static int
Coords(ZnItem		item,
       int		contour __unused,
       int		index __unused,
       int		cmd,
       ZnPoint		**pts,
       char		**controls __unused,
       unsigned int	*num_pts)
{
  TextItem	text = (TextItem) item;
  
  if ((cmd == ZN_COORDS_ADD) || (cmd == ZN_COORDS_ADD_LAST) || (cmd == ZN_COORDS_REMOVE)) {
    Tcl_AppendResult(item->wi->interp,
		     " texts can't add or remove vertices", NULL);
    return TCL_ERROR;
  }
  else if ((cmd == ZN_COORDS_REPLACE) || (cmd == ZN_COORDS_REPLACE_ALL)) {
    if (*num_pts == 0) {
      Tcl_AppendResult(item->wi->interp,
		       " coords command need 1 point on texts", NULL);
      return TCL_ERROR;
    }
    text->pos = (*pts)[0];
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
  else if ((cmd == ZN_COORDS_READ) || (cmd == ZN_COORDS_READ_ALL)) {
    *num_pts = 1;
    *pts = &text->pos;
  }
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Index --
 *	Parse a text index and return its value and a
 *	error status (standard Tcl result).
 *
 **********************************************************************************
 */
static int
PointToChar(TextItem	text,
	    int		x,
	    int		y)
{
  int		i, n, num_lines, dummy, byte_index;
  ZnPoint	p;
  TextLineInfo	ti;
  Tk_FontMetrics fm;
  ZnReal	a, b;

  byte_index = 0;
  if (!text->text_info) {
    return 0;
  }
  
  p.x = x;
  p.y = y;
  a = ZnLineToPointDist(&text->poly[0], &text->poly[2], &p, NULL);
  b = ZnLineToPointDist(&text->poly[0], &text->poly[1], &p, NULL);
  p.x = (text->max_width * b /
	 hypot(text->poly[2].x - text->poly[0].x, text->poly[2].y - text->poly[0].y));
  p.y = (text->height * a /
	 hypot(text->poly[1].x - text->poly[0].x, text->poly[1].y - text->poly[0].y));
  p.x = ZnNearestInt(p.x);
  p.y = ZnNearestInt(p.y);

  /*
   * Point above text, returns index 0.
   */
  if (p.y < 0) {
    return 0;
  }
  
  /*
   * Find the text line under point.
   */
  num_lines = ZnListSize(text->text_info);
  ti = ZnListArray(text->text_info);
  Tk_GetFontMetrics(text->font, &fm);
  for (i = 0; i < num_lines; i++, ti++) {
    if (p.y < ti->origin_y + fm.descent) {
      if (p.x < ti->origin_x) {
	/*
	 * Point to the left of the current line, returns
	 * index of first char.
	 */
	byte_index = ti->start - text->text;
	break;
      }
      if (p.x >= (ti->origin_x + ti->width)) {
	/*
	 * Point to the right of the current line, returns
	 * index past the last char.
	 */
	byte_index = ti->start + ti->num_bytes - text->text;
	break;
      }
      n = Tk_MeasureChars(text->font, ti->start, (int) ti->num_bytes,
			  p.x + 2 - (int) ti->origin_x, TK_PARTIAL_OK,
			  &dummy);
#ifdef PTK_800
      byte_index = (ti->start + n - 1) - text->text;
#else
      byte_index = Tcl_UtfPrev(ti->start + n, ti->start) - text->text;
#endif
      break;
    }
  }
  if (i == num_lines) {
    /*
     * Point below all lines, return the index after
     * the last char in text.
     */
    ti--;
    byte_index = ti->start + ti->num_bytes - text->text;
  }

  return Tcl_NumUtfChars(text->text, byte_index);
}

/*
 * Return a new index from a current index and a
 * move command.
 *
 * 0 end of index line
 * 1 beginning of index line
 * 2 next word or end of word from index
 * 3 previous word or beginning of word from index
 * 4 previous line from index line
 * 5 next line from index line
 *
 */
static int
MoveFromIndex(TextItem	   text,
	      unsigned int char_index,
	      int	   move)
{
  unsigned int	num_lines, byte_index, num_bytes=0;
  unsigned int	line_index, line_start=0;
  TextLineInfo	lines, p;
  char		*strp;

  if (!text->text_info || !text->text) {
    return char_index;
  }
  byte_index = Tcl_UtfAtIndex(text->text, (int) char_index)-text->text;
  num_lines = ZnListSize(text->text_info);
  lines = p = ZnListArray(text->text_info);
  for (line_index = 0; line_index < num_lines; line_index++, p++) {
    line_start = p->start - text->text;
    num_bytes = p->num_bytes;
    if (line_start + num_bytes >= byte_index) {
      break;
    }
  }
  if (line_index == num_lines) {
    line_index--;
    p--;
  }

  switch (move) {
  case 0:
    byte_index = line_start + num_bytes;
    goto convert_it;
  case 1:
    byte_index = line_start;
    goto convert_it;
  case 2:
    strp = &text->text[byte_index];
    while ((strp[1] == ' ') || (strp[1] == '\n')) {
      strp++;
    }
    while ((strp[1] != ' ') && (strp[1] != '\n') && strp[1]) {
      strp++;
    }
    byte_index = strp + 1 - text->text;
    goto convert_it;
  case 3:
    strp = &text->text[byte_index];
    while ((strp != text->text) && ((strp[-1] == ' ') || (strp[-1] == '\n'))) {
      strp--;
    }
    while ((strp != text->text) && (strp[-1] != ' ') && (strp[-1] != '\n')) {
      strp--;
    }
    byte_index = strp - text->text;
    goto convert_it;
  case 4:
    if (line_index > 0) {
      byte_index -= line_start;
      p = &lines[line_index-1];
      byte_index = MIN(byte_index, p->num_bytes);
      line_start = p->start - text->text;
      byte_index += line_start;
    }
    goto convert_it;
  case 5:
    if (line_index < num_lines-1) {
      byte_index -= line_start;
      p = &lines[line_index+1];
      byte_index = MIN(byte_index, p->num_bytes);
      line_start = p->start - text->text;
      byte_index += line_start;
    }
  convert_it:
    char_index = Tcl_NumUtfChars(text->text, (int) byte_index);
  default:
    return char_index;
  }
}

static int
Index(ZnItem	item,
      int	field __unused,
      Tcl_Obj	*index_spec,
      int	*index)
{
  TextItem	text = (TextItem) item;
  ZnWInfo	*wi = item->wi;
  ZnTextInfo	*ti = &wi->text_info;
  unsigned int	length;
  int		c, x, y;
  double	tmp;
  char		*end, *p;

  p = Tcl_GetString(index_spec);
  c = p[0];
  length = strlen(p);
  
  if ((c == 'e') && (length > 1) && (strncmp(p, "end", length) == 0)) {
    *index = text->num_chars;
  }
  else if ((c == 'e') && (length > 1) && (strncmp(p, "eol", length) == 0)) {
    *index = MoveFromIndex(text, text->insert_index, 0);
  }
  else if ((c == 'b') && (length > 1) && (strncmp(p, "bol", length) == 0)) {
    *index = MoveFromIndex(text, text->insert_index, 1);
  }
  else if ((c == 'e') && (length > 1) && (strncmp(p, "eow", length) == 0)) {
    *index = MoveFromIndex(text, text->insert_index, 2);
  }
  else if ((c == 'b') && (length > 1) && (strncmp(p, "bow", length) == 0)) {
    *index = MoveFromIndex(text, text->insert_index, 3);
  }
  else if ((c == 'u') && (strncmp(p, "up", length) == 0)) {
    *index = MoveFromIndex(text, text->insert_index, 4);
  }
  else if ((c == 'd') && (strncmp(p, "down", length) == 0)) {
    *index = MoveFromIndex(text, text->insert_index, 5);
  }
  else if ((c == 'i') && (strncmp(p, "insert", length) == 0)) {
    *index = text->insert_index;
  }
  else if ((c == 's') && (strncmp(p, "sel.first", length) == 0) &&
	   (length >= 5)) {
    if (ti->sel_item != item) {
      Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL);
      return TCL_ERROR;
    }
    *index = ti->sel_first;
  }
  else if ((c == 's') && (strncmp(p, "sel.last", length) == 0) &&
	   (length >= 5)) {
    if (ti->sel_item != item) {
      Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL);
      return TCL_ERROR;
    }
    /*
     * We return a modified selection end so that it reflect
     * the text index of the last character _not_ the insertion
     * point between the last and the next.
     */
    *index = ti->sel_last-1;
  }
  else if (c == '@') {
    p++;
    tmp = strtod(p, &end);
    if ((end == p) || (*end != ',')) {
      goto badIndex;
    }
    /*x = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);*/
    x = (int) tmp;
    p = end+1;
    tmp = strtod(p, &end);
    if ((end == p) || (*end != 0)) {
      goto badIndex;
    }
    /*y = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);*/
    y = (int) tmp;
    
    *index = PointToChar(text, x, y);
  }
  else if (Tcl_GetIntFromObj(wi->interp, index_spec, index) == TCL_OK) {
    if (*index < 0){
      *index = 0;
    }
    else if ((unsigned int) *index > text->num_chars) {
      *index = text->num_chars;
    }
  }
  else {
  badIndex:
    Tcl_AppendResult(wi->interp, "bad index \"", p, "\"", (char *) NULL);
    return TCL_ERROR;
  }

  return TCL_OK;  
}


/*
 **********************************************************************************
 *
 * InsertChars --
 *
 **********************************************************************************
 */
static void
InsertChars(ZnItem	item,
	    int		field __unused,
	    int		*index,
	    char	*chars)
{
  TextItem	text = (TextItem) item;
  ZnTextInfo	*ti = &item->wi->text_info;
  unsigned int	num_chars, byte_index, num_bytes = strlen(chars);
  char		*new;

  if (num_bytes == 0) {
    return;
  }
  if (*index < 0) {
    *index = 0;
  }
  if ((unsigned int) *index > text->num_chars) {
    *index = text->num_chars;
  }
  num_chars = Tcl_NumUtfChars(chars, (int) num_bytes);

  if (text->text) {
    byte_index = Tcl_UtfAtIndex(text->text, *index)-text->text;
    new = ZnMalloc(strlen(text->text) + num_bytes + 1);
    memcpy(new, text->text, (size_t) byte_index);
    strcpy(new + byte_index + num_bytes, text->text + byte_index);
    ZnFree(text->text);
  }
  else {
    byte_index = 0;
    new = ZnMalloc(num_bytes + 1);
    new[num_bytes] = 0;
  }
  memcpy(new + byte_index, chars, num_bytes);
  text->text = new;
  text->num_chars += num_chars;
  
  if (text->insert_index >= (unsigned int) *index) {
    text->insert_index += num_chars;
  }
  if (ti->sel_item == item) {
    if (ti->sel_first >= *index) {
      ti->sel_first += num_chars;
    }
    if (ti->sel_last >= *index) {
      ti->sel_last += num_chars;
    }
    if ((ti->anchor_item == item) && (ti->sel_anchor >= *index)) {
      ti->sel_anchor += num_chars;
    }
  }
  
  ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG);
}


/*
 **********************************************************************************
 *
 * DeleteChars --
 *
 **********************************************************************************
 */
static void
DeleteChars(ZnItem	item,
	    int		field __unused,
	    int		*first,
	    int		*last)
{
  TextItem	text = (TextItem) item;
  int		byte_count, first_offset;
  int		char_count, num_bytes;
  ZnTextInfo	*ti = &item->wi->text_info;
  char		*new;
  
  if (!text->text) {
    return;
  }
  if (*first < 0) {
    *first = 0;
  }
  if (*last >= (int) text->num_chars) {
    *last = text->num_chars-1;
  }
  if (*first > *last) {
    return;
  }
  char_count = *last + 1 - *first;
  first_offset = Tcl_UtfAtIndex(text->text, *first)-text->text;
  byte_count = Tcl_UtfAtIndex(text->text + first_offset, char_count)-
    (text->text+first_offset);
  num_bytes = strlen(text->text);

  if (num_bytes - byte_count) {
    new = (char *) ZnMalloc((unsigned) (num_bytes + 1 - byte_count));
    memcpy(new, text->text, (size_t) first_offset);
    strcpy(new + first_offset, text->text + first_offset + byte_count);
    ZnFree(text->text);
    text->text = new;
    text->num_chars -= char_count;
  }
  else {
    ZnFree(text->text);
    text->text = NULL;
    text->num_chars = 0;
  }

  if (text->insert_index > (unsigned int) *first) {
    text->insert_index -= char_count;
    if (text->insert_index < (unsigned int) *first) {
      text->insert_index = *first;
    }
    else if (*first == 0) {
      text->insert_index = 0;
    }
  }
  if (ti->sel_item == item) {
    if (ti->sel_first > *first) {
      ti->sel_first -= char_count;
      if (ti->sel_first < *first) {
	ti->sel_first = *first;
      }
    }
    if (ti->sel_last >= *first) {
      ti->sel_last -= char_count;
      if (ti->sel_last < *first - 1) {
	ti->sel_last = *first - 1;
      }
    }
    if (ti->sel_first > ti->sel_last) {
      ti->sel_item = ZN_NO_ITEM;
    }
    if ((ti->anchor_item == item) && (ti->sel_anchor > *first)) {
      ti->sel_anchor -= char_count;
      if (ti->sel_anchor < *first) {
	ti->sel_anchor = *first;
      }
    }
  }

  ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG);
}


/*
 **********************************************************************************
 *
 * Cursor --
 *
 **********************************************************************************
 */
static void
TextCursor(ZnItem	item,
	   int		field __unused,
	   int		index)
{
  TextItem	text = (TextItem) item;

  if (index < 0) {
    text->insert_index = 0;
  }
  else if ((unsigned int) index > text->num_chars) {
    text->insert_index = text->num_chars;
  }
  else {
    text->insert_index = index;
  }
}


/*
 **********************************************************************************
 *
 * Selection --
 *
 **********************************************************************************
 */
static int
Selection(ZnItem	item,
	  int		field __unused,
	  int		offset,
	  char		*chars,
	  int		max_bytes)
{
  TextItem	text = (TextItem) item;
  ZnWInfo	*wi = item->wi;
  ZnTextInfo	*ti = &wi->text_info;
  int		count;
  char	const	*sel_first, *sel_last;

  if (!text->text) {
    return 0;
  }
  if ((ti->sel_first < 0) ||
      (ti->sel_first > ti->sel_last)) {
    return 0;
  }
  sel_first = Tcl_UtfAtIndex(text->text, ti->sel_first);
  sel_last = Tcl_UtfAtIndex(sel_first, ti->sel_last + 1 - ti->sel_first);
  count = sel_last - sel_first - offset;
  if (count <= 0) {
    return 0;
  }
  if (count > max_bytes) {
    count = max_bytes;
  }
  memcpy(chars, sel_first + offset, (size_t) count);
  chars[count] = 0;
  
  return count;
}


/*
 **********************************************************************************
 *
 * Exported functions struct
 *
 **********************************************************************************
 */
static ZnItemClassStruct TEXT_ITEM_CLASS = {
  sizeof(TextItemStruct),
  0,			/* num_parts */
  True,			/* has_anchors */
  "text",
  text_attrs,
  Tk_Offset(TextItemStruct, pos),
  Init,
  Clone,
  Destroy,
  Configure,
  Query,
  NULL,			/* GetFieldSet */
  GetAnchor,
  GetClipVertices,
  NULL,			/* GetContours */
  Coords,
  InsertChars,
  DeleteChars,
  TextCursor,
  Index,
  NULL,			/* Part */
  Selection,
  NULL,			/* Contour */
  ComputeCoordinates,
  ToArea,
  Draw,
  Render,
  IsSensitive,
  Pick,
  NULL,			/* PickVertex */
  PostScript
};

ZnItemClassId ZnText = (ZnItemClassId) &TEXT_ITEM_CLASS;
