/*
 * Rectangle.c -- Implementation of rectangle item.
 *
 * Authors              : Patrick Lecoanet.
 * Creation date        : Fri Dec  2 14:47:42 1994
 *
 * $Id: Rectangle.c,v 1.71 2005/05/10 07:59:48 lecoanet Exp $
 */

/*
 *  Copyright (c) 1994 - 2005 CENA, Patrick Lecoanet --
 *
 * See the file "Copyright" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */


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


static const char rcsid[] = "$Id: Rectangle.c,v 1.71 2005/05/10 07:59:48 lecoanet Exp $";
static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";

/*
 * Bit offset of flags.
 */
#define FILLED_BIT      1       /* If the rectangle is filled with color/pattern */
#define ALIGNED_BIT     2


/*
 **********************************************************************************
 *
 * Specific Rectangle item record
 *
 **********************************************************************************
 */

typedef struct _RectangleItemStruct {
  ZnItemStruct  header;

  /* Public data */
  ZnPoint       coords[2];
  unsigned short flags;
  ZnReliefStyle relief;
  ZnLineStyle   line_style;
  ZnDim         line_width;
  ZnGradient    *line_color;
  ZnImage       line_pattern;
  ZnGradient    *fill_color;
  ZnImage       tile;
  
  /* Private data */
  ZnPoint       dev[4];
  ZnGradient    *gradient;
  ZnPoint       *grad_geo;
} RectangleItemStruct, *RectangleItem;


static ZnAttrConfig     rect_attrs[] = {
  { ZN_CONFIG_BOOL, "-composealpha", NULL,
    Tk_Offset(RectangleItemStruct, header.flags), ZN_COMPOSE_ALPHA_BIT,
    ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-composerotation", NULL,
    Tk_Offset(RectangleItemStruct, header.flags), ZN_COMPOSE_ROTATION_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BOOL, "-composescale", NULL,
    Tk_Offset(RectangleItemStruct, header.flags), ZN_COMPOSE_SCALE_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-fillcolor", NULL,
    Tk_Offset(RectangleItemStruct, fill_color), 0,
    ZN_COORDS_FLAG|ZN_BORDER_FLAG, False },
  { ZN_CONFIG_BOOL, "-filled", NULL,
    Tk_Offset(RectangleItemStruct, flags), FILLED_BIT, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BITMAP, "-fillpattern", NULL,
    Tk_Offset(RectangleItemStruct, tile), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-linecolor", NULL,
    Tk_Offset(RectangleItemStruct, line_color), 0,
    ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BITMAP, "-linepattern", NULL,
    Tk_Offset(RectangleItemStruct, line_pattern), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_LINE_STYLE, "-linestyle", NULL,
    Tk_Offset(RectangleItemStruct, line_style), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_DIM, "-linewidth", NULL,
    Tk_Offset(RectangleItemStruct, line_width), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_PRI, "-priority", NULL,
    Tk_Offset(RectangleItemStruct, header.priority), 0,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG, False },
  { ZN_CONFIG_RELIEF, "-relief", NULL, Tk_Offset(RectangleItemStruct, relief), 0,
    ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-sensitive", NULL,
    Tk_Offset(RectangleItemStruct, header.flags), ZN_SENSITIVE_BIT,
    ZN_REPICK_FLAG, False },
  { ZN_CONFIG_TAG_LIST, "-tags", NULL,
    Tk_Offset(RectangleItemStruct, header.tags), 0, 0, False },
  { ZN_CONFIG_IMAGE, "-tile", NULL,
    Tk_Offset(RectangleItemStruct, tile), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-visible", NULL,
    Tk_Offset(RectangleItemStruct, header.flags), ZN_VISIBLE_BIT,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False },
  
  { ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False }
};



/*
 **********************************************************************************
 *
 * Init --
 *
 **********************************************************************************
 */
static int
Init(ZnItem             item,
     int                *argc,
     Tcl_Obj *CONST     *args[])
{
  ZnWInfo       *wi = item->wi;
  RectangleItem rect = (RectangleItem) item;
  unsigned int  num_points;
  ZnPoint       *points;

  rect->gradient = NULL;
  rect->grad_geo = NULL;

  /* Init attributes */
  SET(item->flags, ZN_VISIBLE_BIT);
  SET(item->flags, ZN_SENSITIVE_BIT);
  SET(item->flags, ZN_COMPOSE_ALPHA_BIT);
  SET(item->flags, ZN_COMPOSE_ROTATION_BIT);
  SET(item->flags, ZN_COMPOSE_SCALE_BIT);
  item->priority = 1;
  
  if (*argc < 1) {
    Tcl_AppendResult(wi->interp, " rectangle coords expected", NULL);
    return TCL_ERROR;
  }
  if (ZnParseCoordList(wi, (*args)[0], &points,
                       NULL, &num_points, NULL) == TCL_ERROR) {
    return TCL_ERROR;
  }
  if (num_points != 2) {
    Tcl_AppendResult(wi->interp, " malformed rectangle coords", NULL);
    return TCL_ERROR;
  };
  rect->coords[0] = points[0];
  rect->coords[1] = points[1];
  (*args)++;
  (*argc)--;
  
  CLEAR(rect->flags, FILLED_BIT);
  rect->relief = ZN_RELIEF_FLAT;
  rect->line_style = ZN_LINE_SIMPLE;
  rect->line_width = 1;
  rect->line_pattern = ZnUnspecifiedImage;
  rect->tile = ZnUnspecifiedImage;
  rect->line_color = ZnGetGradientByValue(wi->fore_color);
  rect->fill_color = ZnGetGradientByValue(wi->fore_color);
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Clone --
 *
 **********************************************************************************
 */
static void
Clone(ZnItem    item)
{
  RectangleItem rect = (RectangleItem) item;

  if (rect->gradient) {
    rect->gradient = ZnGetGradientByValue(rect->gradient);
  }
  if (rect->tile != ZnUnspecifiedImage) {
    rect->tile = ZnGetImageByValue(rect->tile, ZnUpdateItemImage, item);
  }
  if (rect->line_pattern != ZnUnspecifiedImage) {
    rect->line_pattern = ZnGetImageByValue(rect->line_pattern, NULL, NULL);
  }
  rect->line_color = ZnGetGradientByValue(rect->line_color);
  rect->fill_color = ZnGetGradientByValue(rect->fill_color);
  rect->grad_geo = NULL;
}


/*
 **********************************************************************************
 *
 * Destroy --
 *
 **********************************************************************************
 */
static void
Destroy(ZnItem  item)
{
  RectangleItem rect = (RectangleItem) item;

  if (rect->tile != ZnUnspecifiedImage) {
    ZnFreeImage(rect->tile, ZnUpdateItemImage, item);
    rect->tile = ZnUnspecifiedImage;
  }
  if (rect->gradient) {
    ZnFreeGradient(rect->gradient);
  }
  if (rect->line_pattern != ZnUnspecifiedImage) {
    ZnFreeImage(rect->line_pattern, NULL, NULL);
    rect->line_pattern = ZnUnspecifiedImage;
  }
  if (rect->grad_geo) {
    ZnFree(rect->grad_geo);
  }
  ZnFreeGradient(rect->fill_color);
  ZnFreeGradient(rect->line_color);
}


/*
 **********************************************************************************
 *
 * Configure --
 *
 **********************************************************************************
 */
static int
Configure(ZnItem        item,
          int           argc,
          Tcl_Obj *CONST argv[],
          int           *flags)
{
  ZnWInfo       *wi = item->wi;
  RectangleItem rect = (RectangleItem) item;
  int           status = TCL_OK;
  XColor        *color;
  unsigned short alpha;
  
  status = ZnConfigureAttributes(wi, item, item, rect_attrs, argc, argv, flags);
  
  if (rect->gradient &&
      (ISSET(*flags, ZN_BORDER_FLAG) || (rect->relief == ZN_RELIEF_FLAT))) {
    ZnFreeGradient(rect->gradient);
    rect->gradient = NULL;
  }
  if ((rect->relief != ZN_RELIEF_FLAT) && !rect->gradient) {
    color = ZnGetGradientColor(rect->line_color, 51.0, &alpha);
    rect->gradient = ZnGetReliefGradient(wi->interp, wi->win,
                                         Tk_NameOfColor(color), alpha);
    if (rect->gradient == NULL) {
      status = TCL_ERROR;
    }
  }

  return status;
}


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

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * ComputeCoordinates --
 *
 **********************************************************************************
 */
static void
ComputeCoordinates(ZnItem       item,
                   ZnBool       force)
{
  ZnWInfo       *wi = item->wi;
  RectangleItem rect = (RectangleItem) item;
  ZnPoint       p[4];
  int           i;
  ZnBool        aligned;
  ZnDim         delta, lw2;
  
  ZnResetBBox(&item->item_bounding_box);
  if (!rect->line_width && ISCLEAR(rect->flags, FILLED_BIT)) {
    return;
  }

  p[0] = rect->coords[0];
  p[2] = rect->coords[1];
  p[1].x = p[2].x;
  p[1].y = p[0].y;
  p[3].x = p[0].x;
  p[3].y = p[2].y;
  ZnTransformPoints(wi->current_transfo, p, rect->dev, 4);
  for (i = 0; i < 4; i++) {
    rect->dev[i].x = ZnNearestInt(rect->dev[i].x);
    rect->dev[i].y = ZnNearestInt(rect->dev[i].y);
  }

  /*
   * Add all points to the bounding box. Then expand by the line
   * width to account for mitered corners. This is an overestimate.
   */
  ZnAddPointsToBBox(&item->item_bounding_box, rect->dev, 4);
  if (rect->line_width > 0) {
    lw2 = rect->line_width/2.0;
    item->item_bounding_box.orig.x -= lw2;
    item->item_bounding_box.orig.y -= lw2;
    item->item_bounding_box.corner.x += lw2;
    item->item_bounding_box.corner.y += lw2;
  }
  item->item_bounding_box.orig.x -= 0.5;
  item->item_bounding_box.orig.y -= 0.5;
  item->item_bounding_box.corner.x += 0.5;
  item->item_bounding_box.corner.y += 0.5;
  
  delta = rect->dev[0].y - rect->dev[1].y;
  delta = ABS(delta);
  aligned = delta < X_PRECISION_LIMIT;
  delta = rect->dev[0].x - rect->dev[3].x;
  delta = ABS(delta);
  aligned &= delta < X_PRECISION_LIMIT;
  ASSIGN(rect->flags, ALIGNED_BIT, aligned);
  
#ifdef GL
  /*
   * Compute the gradient geometry
   */
  if (!ZnGradientFlat(rect->fill_color)) {
    ZnPoly      shape;
    
    if (rect->fill_color->type == ZN_AXIAL_GRADIENT) {
      int       angle = rect->fill_color->angle;
      
      if ((angle != 0) && (angle != 90) && (angle != 180) && (angle != 270)) {
        if (!rect->grad_geo) {
          rect->grad_geo = ZnMalloc(6*sizeof(ZnPoint));
        }
        ZnPolyContour1(&shape, p, 4, False);
        ZnComputeGradient(rect->fill_color, wi, &shape, rect->grad_geo);
      }
      else {
        goto free_ggeo;
      }
    }
    else {
      if (!rect->grad_geo) {
        rect->grad_geo = ZnMalloc(6*sizeof(ZnPoint));
      }
      if (rect->fill_color->type == ZN_PATH_GRADIENT) {
        ZnPolyContour1(&shape, rect->coords, 2, False);
      }
      else {
        ZnPolyContour1(&shape, p, 4, False);
      }
      ZnComputeGradient(rect->fill_color, wi, &shape, rect->grad_geo);
    }
  }
  else {
  free_ggeo:
    if (rect->grad_geo) {
      ZnFree(rect->grad_geo);
      rect->grad_geo = NULL;
    }
  }
#endif
}


/*
 **********************************************************************************
 *
 * ToArea --
 *      Tell if the object is entirely outside (-1),
 *      entirely inside (1) or in between (0).
 *
 **********************************************************************************
 */
static int
ToArea(ZnItem   item,
       ZnToArea ta)
{
  RectangleItem rect = (RectangleItem) item;
  int           result, result2;
  ZnBBox        *area = ta->area;

  result = -1;

  if (ISSET(rect->flags, FILLED_BIT)) {
    result = ZnPolygonInBBox(rect->dev, 4, area, NULL);
    if (result == 0) {
      return 0;
    }
  }
  if (rect->line_width > 0) {
    int         i;
    ZnPoint     pts[5];

    for (i = 0; i < 4; i++) {
      pts[i] = rect->dev[i];
    }
    pts[4] = pts[0];
    result2 = ZnPolylineInBBox(pts, 5, rect->line_width,
                               CapProjecting, JoinMiter, area);
    if (ISCLEAR(rect->flags, FILLED_BIT)) {
      if (result2 == 0) {
        return 0;
      }
      result = result2;
    }
    else if (result2 != result) {
      return 0;
    }
  }

  return result;
}


/*
 **********************************************************************************
 *
 * Draw --
 *
 **********************************************************************************
 */
static void
Draw(ZnItem     item)
{
  ZnWInfo       *wi  = item->wi;
  RectangleItem rect = (RectangleItem) item;
  XGCValues     values;
  unsigned int  i, gc_mask;
  XRectangle    r;
  XPoint        xp[5];
  
  if (ISSET(rect->flags, ALIGNED_BIT)) {
    if (rect->dev[0].x < rect->dev[2].x) {
      r.x = (int) rect->dev[0].x;
      r.width = ((int) rect->dev[2].x) - r.x;
    }
    else {
      r.x = (int) rect->dev[2].x;
      r.width = ((int) rect->dev[0].x) - r.x;
    }
    if (rect->dev[0].y < rect->dev[2].y) {
      r.y = (int) rect->dev[0].y;
      r.height = ((int) rect->dev[2].y) - r.y;
    }
    else {
      r.y = (int) rect->dev[2].y;
      r.height = ((int) rect->dev[0].y) - r.y;
    }
  }
  else {
    for (i = 0; i < 4; i++) {
      xp[i].x = (int) rect->dev[i].x;
      xp[i].y = (int) rect->dev[i].y;
    }
    xp[i] = xp[0];
  }
  
  /*
   * Fill if requested.
   */
  if (ISSET(rect->flags, FILLED_BIT)) {
    values.foreground = ZnGetGradientPixel(rect->fill_color, 0.0);
    if (rect->tile != ZnUnspecifiedImage) {
      if (!ZnImageIsBitmap(rect->tile)) { /* Fill tiled */
        values.fill_style = FillTiled;
        values.tile = ZnImagePixmap(rect->tile, wi->win);
        if (ISSET(rect->flags, ALIGNED_BIT)) {
          values.ts_x_origin = (int) r.x;
          values.ts_y_origin = (int) r.y;
        }
        else {
          values.ts_x_origin = (int) item->item_bounding_box.orig.x;
          values.ts_y_origin = (int) item->item_bounding_box.orig.y;
        }
        XChangeGC(wi->dpy, wi->gc,
                  GCTileStipXOrigin|GCTileStipYOrigin|GCFillStyle|GCTile, &values);
      }
      else {
        values.fill_style = FillStippled;
        values.stipple = ZnImagePixmap(rect->tile, wi->win);
        if (ISSET(rect->flags, ALIGNED_BIT)) {
          values.ts_x_origin = (int) r.x;
          values.ts_y_origin = (int) r.y;
        }
        else {
          values.ts_x_origin = (int) item->item_bounding_box.orig.x;
          values.ts_y_origin = (int) item->item_bounding_box.orig.y;
        }
        XChangeGC(wi->dpy, wi->gc,
                  GCTileStipXOrigin|GCTileStipYOrigin|GCFillStyle|GCStipple|GCForeground,
                  &values);
      }
    }
    else { /* Fill solid */
      values.fill_style = FillSolid;
      XChangeGC(wi->dpy, wi->gc, GCForeground | GCFillStyle, &values);
    }
    if (ISSET(rect->flags, ALIGNED_BIT)) {
      XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, r.x, r.y,
                     r.width, r.height);
    }
    else {
      XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin);
    }
  }

  /* Draw the outline */
  if (rect->line_width) {
    if (rect->relief != ZN_RELIEF_FLAT) {
      if (ISSET(rect->flags, ALIGNED_BIT)) {
        ZnDrawRectangleRelief(wi, rect->relief, rect->gradient,
                              &r, rect->line_width);
      }
      else {
        ZnPoint p[5];
        for (i = 0; i < 4; i++) {
          p[4-i].x = rect->dev[i].x;
          p[4-i].y = rect->dev[i].y;
        }
        p[0] = p[4];
        ZnDrawPolygonRelief(wi, rect->relief, rect->gradient,
                            p, 5, rect->line_width);
      }
    }
    else {
      ZnSetLineStyle(wi, rect->line_style);
      gc_mask = GCFillStyle|GCLineWidth|GCForeground|GCJoinStyle;
      values.foreground = ZnGetGradientPixel(rect->line_color, 0.0);
      values.line_width = (rect->line_width == 1) ? 0 : (int) rect->line_width;
      values.join_style = JoinMiter;
      if (ISCLEAR(rect->flags, ALIGNED_BIT)) {
        gc_mask |= GCCapStyle;
        values.cap_style = CapProjecting;
      }
      if (rect->line_pattern == ZnUnspecifiedImage) {
        values.fill_style = FillSolid;
        XChangeGC(wi->dpy, wi->gc, gc_mask, &values);
      }
      else {
        values.fill_style = FillStippled;
        values.stipple = ZnImagePixmap(rect->line_pattern, wi->win);
        gc_mask |= GCStipple;
        XChangeGC(wi->dpy, wi->gc, gc_mask, &values);
      }
      if (ISSET(rect->flags, ALIGNED_BIT)) {
        XDrawRectangle(wi->dpy, wi->draw_buffer, wi->gc, r.x, r.y,
                       r.width, r.height);
      }
      else {
        XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, xp, 5, CoordModeOrigin);
      }
    }
  }
}


/*
 **********************************************************************************
 *
 * Render --
 *
 **********************************************************************************
 */
#ifdef GL
static void
RectRenderCB(void *closure)
{
  RectangleItem rect = (RectangleItem) closure;

  glBegin(GL_TRIANGLE_STRIP);
  glVertex2d(rect->dev[0].x, rect->dev[0].y);
  glVertex2d(rect->dev[3].x, rect->dev[3].y);
  glVertex2d(rect->dev[1].x, rect->dev[1].y);
  glVertex2d(rect->dev[2].x, rect->dev[2].y);
  glEnd();
}
#endif

#ifdef GL
static void
Render(ZnItem   item)
{
  ZnWInfo       *wi  = item->wi;
  RectangleItem rect = (RectangleItem) item;
  int           i;

#ifdef GL_LIST
  if (!item->gl_list) {
    item->gl_list = glGenLists(1);
    glNewList(item->gl_list, GL_COMPILE);
#endif
    if (ISSET(rect->flags, FILLED_BIT)) {
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      
      if (!ZnGradientFlat(rect->fill_color)) {
        ZnBool  fast = (rect->fill_color->type == ZN_AXIAL_GRADIENT) && !rect->grad_geo;
        ZnPoly  poly;
        
        ZnPolyContour1(&poly, rect->dev, 4, False);
        ZnRenderGradient(wi, rect->fill_color,
                         fast ? NULL: RectRenderCB, rect,
                         fast ? rect->dev : rect->grad_geo, &poly);
      }
      else if (rect->tile != ZnUnspecifiedImage) { /* Fill tiled/patterned */
        if (ISSET(rect->flags, ALIGNED_BIT)) {
          ZnBBox bbox;

          bbox.orig = rect->dev[0];
          bbox.corner = rect->dev[2];
          ZnRenderTile(wi, rect->tile, rect->fill_color, NULL, NULL, (ZnPoint *) &bbox);
        }
        else {
          ZnRenderTile(wi, rect->tile, rect->fill_color, RectRenderCB,
                       rect, (ZnPoint *) &item->item_bounding_box);
        }
      }
      else {
        unsigned short alpha;
        XColor *color = ZnGetGradientColor(rect->fill_color, 0.0, &alpha);
        alpha = ZnComposeAlpha(alpha, wi->alpha);
        glColor4us(color->red, color->green, color->blue, alpha);
        RectRenderCB(rect);
      }
    }
    
    if (rect->line_width) {
      ZnPoint p[5];
      for (i = 0; i < 4; i++) {
        p[4-i].x = rect->dev[i].x;
        p[4-i].y = rect->dev[i].y;
      }
      p[0] = p[4];
      if (rect->relief != ZN_RELIEF_FLAT) {
        ZnRenderPolygonRelief(wi, rect->relief, rect->gradient, False,
                              p, 5, rect->line_width);
      }
      else {
        ZnRenderPolyline(wi, p, 5, rect->line_width,
                         rect->line_style, CapRound, JoinMiter,
                         NULL, NULL, rect->line_color);
      }
    }
#ifdef GL_LIST    
    glEndList();
  }
  
  glCallList(item->gl_list);
#endif
}
#else
static void
Render(ZnItem   item)
{
}
#endif
 

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


/*
 **********************************************************************************
 *
 * Pick --
 *
 **********************************************************************************
 */
static double
Pick(ZnItem     item,
     ZnPick     ps)
{
  RectangleItem rect = (RectangleItem) item;
  double        best_dist;
  ZnPoint       *p = ps->point;

  best_dist = ZnPolygonToPointDist(rect->dev, 4, p);

  if (ISSET(rect->flags, FILLED_BIT)) {
    if (best_dist <= 0.0) {
      return 0.0;
    }
  }
  best_dist = ABS(best_dist);
  
  if (rect->line_width > 1) {
    double      dist;
    int         i;
    ZnPoint     pts[5];

    for (i = 0; i < 4; i++) {
      pts[i] = rect->dev[i];
    }
    pts[4] = pts[0];
    dist = ZnPolylineToPointDist(pts, 5, rect->line_width,
                                 CapProjecting, JoinMiter, p);
    if (dist <= 0.0) {
      return 0.0;
    }
    best_dist = MIN(dist, best_dist);
  }

  return best_dist;
}


/*
 **********************************************************************************
 *
 * PostScript --
 *
 **********************************************************************************
 */
static int
PostScript(ZnItem item,
           ZnBool prepass,
           ZnBBox *area)
{
  ZnWInfo       *wi = item->wi;
  RectangleItem rect = (RectangleItem) item;
  char          path[500];

  if (ISCLEAR(rect->flags, FILLED_BIT) && (rect->line_width == 0)) {
    return TCL_OK;
  }

  /*
   * Create the rectangle path.
   */
  sprintf(path, "%.15g %.15g moveto %.15g %.15g lineto %.15g %.15g lineto %.15g %.15g lineto closepath\n",
          rect->dev[0].x, rect->dev[0].y, rect->dev[1].x, rect->dev[1].y,
          rect->dev[2].x, rect->dev[2].y, rect->dev[3].x, rect->dev[3].y);
  Tcl_AppendResult(wi->interp, path, NULL);

  /*
   * Emit code to draw the filled area.
   */
  if (ISSET(rect->flags, FILLED_BIT)) {
    if (rect->line_width) {
      Tcl_AppendResult(wi->interp, "gsave\n", NULL);
    }
    if (!ZnGradientFlat(rect->fill_color)) {
      if (ZnPostscriptGradient(wi->interp, wi->ps_info, rect->fill_color,
                               rect->grad_geo ? rect->grad_geo : rect->dev, NULL) != TCL_OK) {
        return TCL_ERROR;
      }
    }
    else if (rect->tile != ZnUnspecifiedImage) {
      if (!ZnImageIsBitmap(rect->tile)) { /* Fill tiled */
        if (ZnPostscriptTile(wi->interp, wi->win, wi->ps_info, rect->tile) != TCL_OK) {
          return TCL_ERROR;
        }
      }
      else { /* Fill stippled */
        if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                               ZnGetGradientColor(rect->fill_color, 0.0, NULL)) != TCL_OK) {
          return TCL_ERROR;
        }
        Tcl_AppendResult(wi->interp, "clip ", NULL);
        if (ZnPostscriptStipple(wi->interp, wi->win, wi->ps_info, rect->tile) != TCL_OK) {
          return TCL_ERROR;
        }
      }
    }
    else { /* Fill solid */
      if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                             ZnGetGradientColor(rect->fill_color, 0.0, NULL)) != TCL_OK) {
        return TCL_ERROR;
      }
      Tcl_AppendResult(wi->interp, "fill\n", NULL);
    }
    if (rect->line_width) {
      Tcl_AppendResult(wi->interp, "grestore\n", NULL);
    }
  }

  /*
   * Then emit code code to stroke the outline.
   */
  if (rect->line_width) {
    if (rect->relief != ZN_RELIEF_FLAT) {
      /* TODO No support yet */
    }
    else {
      Tcl_AppendResult(wi->interp, "0 setlinejoin 2 setlinecap\n", NULL);
      if (ZnPostscriptOutline(wi->interp, wi->ps_info, wi->win,
                              rect->line_width, rect->line_style,
                              rect->line_color, rect->line_pattern) != TCL_OK) {
        return TCL_ERROR;
      }
    }
  }

  return TCL_OK;
}


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

  if (ISSET(rect->flags, ALIGNED_BIT)) {
    ZnListAssertSize(ZnWorkPoints, 2);
    points = ZnListArray(ZnWorkPoints);
    ZnTriStrip1(tristrip, points, 2, False);
    tristrip->strips[0].fan = False;
  
    if (rect->dev[0].x < rect->dev[2].x) {
      points[0].x = rect->dev[0].x;
      points[1].x = rect->dev[2].x+1.0;
    }
    else {
      points[0].x = rect->dev[2].x;
      points[1].x = rect->dev[0].x+1.0;
    }
    if (rect->dev[0].y < rect->dev[2].y) {
      points[0].y = rect->dev[0].y;
      points[1].y = rect->dev[2].y+1.0;
    }
    else {
      points[0].y = rect->dev[2].y;
      points[1].y = rect->dev[0].y+1.0;
    }
  }
  else {
    ZnListAssertSize(ZnWorkPoints, 4);
    points = ZnListArray(ZnWorkPoints);
    points[0] = rect->dev[1];
    points[1] = rect->dev[2];
    points[2] = rect->dev[0];
    points[3] = rect->dev[3];
    ZnTriStrip1(tristrip, points, 4, False);
  }

  return ISSET(rect->flags, ALIGNED_BIT);
}


/*
 **********************************************************************************
 *
 * Coords --
 *      Return or edit the item vertices.
 *
 **********************************************************************************
 */
static int
Coords(ZnItem           item,
       int              contour,
       int              index,
       int              cmd,
       ZnPoint          **pts,
       char             **controls,
       unsigned int     *num_pts)
{
  RectangleItem rect = (RectangleItem) item;

  if ((cmd == ZN_COORDS_ADD) || (cmd == ZN_COORDS_ADD_LAST) || (cmd == ZN_COORDS_REMOVE)) {
    Tcl_AppendResult(item->wi->interp,
                     " rectangles can't add or remove vertices", NULL);
    return TCL_ERROR;
  }
  else if (cmd == ZN_COORDS_REPLACE_ALL) {
    if (*num_pts != 2) {
      Tcl_AppendResult(item->wi->interp,
                       " coords command need 2 points on rectangles", NULL);
      return TCL_ERROR;
    }
    rect->coords[0] = (*pts)[0];
    rect->coords[1] = (*pts)[1];
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
  else if (cmd == ZN_COORDS_REPLACE) {
    if (*num_pts < 1) {
      Tcl_AppendResult(item->wi->interp,
                       " coords command need at least 1 point", NULL);
      return TCL_ERROR;
    }
    if (index < 0) {
      index += 2;
    }
    if ((index < 0) || (index > 1)) {
    range_err:
      Tcl_AppendResult(item->wi->interp,
                       " incorrect coord index, should be between -2 and 1", NULL);
      return TCL_ERROR;
    }
    rect->coords[index] = (*pts)[0];
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
  else if (cmd == ZN_COORDS_READ_ALL) {
    *num_pts = 2;
    *pts = rect->coords;
  }
  else if (cmd == ZN_COORDS_READ) {
    if (index < 0) {
      index += 2;
    }
    if ((index < 0) || (index > 1)) {
      goto range_err;
    }
    *num_pts = 1;
    *pts = &rect->coords[index];
  }

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * GetAnchor --
 *
 **********************************************************************************
 */
static void
GetAnchor(ZnItem        item,
          Tk_Anchor     anchor,
          ZnPoint       *p)
{
  ZnBBox *bbox = &item->item_bounding_box;

  ZnOrigin2Anchor(&bbox->orig,
                  bbox->corner.x - bbox->orig.x,
                  bbox->corner.y - bbox->orig.y,
                  anchor, p);
}


/*
 **********************************************************************************
 *
 * Exported functions struct --
 *
 **********************************************************************************
 */ 
static ZnItemClassStruct RECTANGLE_ITEM_CLASS = {
  "rectangle",
  sizeof(RectangleItemStruct),
  rect_attrs,
  0,                    /* num_parts */
  0,                    /* flags */
  -1,
  Init,
  Clone,
  Destroy,
  Configure,
  Query,
  NULL,                 /* GetFieldSet */
  GetAnchor,
  GetClipVertices,
  NULL,                 /* GetContours */
  Coords,
  NULL,                 /* InsertChars */
  NULL,                 /* DeleteChars */
  NULL,                 /* Cursor */
  NULL,                 /* Index */
  NULL,                 /* Part */
  NULL,                 /* Selection */
  NULL,                 /* Contour */
  ComputeCoordinates,
  ToArea,
  Draw,
  Render,
  IsSensitive,
  Pick,
  NULL,                 /* PickVertex */
  PostScript
};

ZnItemClassId ZnRectangle = (ZnItemClassId) &RECTANGLE_ITEM_CLASS;