/*
 * Draw.c -- Implementation of common drawing routines.
 *
 * Authors		: Patrick Lecoanet.
 * Creation date	: Sat Dec 10 12:51:30 1994
 *
 * $Id: Draw.c,v 1.56 2004/03/24 15:06:44 lecoanet Exp $
 */

/*
 *  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.
 *
 */


/*
 **********************************************************************************
 *
 * The algorihms used to draw the arrows, to do the 3d effects and to
 * smooth the polygons are adapted from Tk.
 *
 **********************************************************************************
 */

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

#include <math.h>
#include <stdarg.h>


#define	POLYGON_RELIEF_DRAW	0
#define	POLYGON_RELIEF_RENDER	1
#define	POLYGON_RELIEF_DIST	2
#define	POLYGON_RELIEF_BBOX	3
#define	POLYGON_RELIEF_IN_BBOX	4

#define	TOP_CONTRAST		13
#define BOTTOM_CONTRAST		6
#define MAX_INTENSITY		65535

#define ARROW_SHAPE_B		10.0
#define ARROW_SHAPE_C		5.0
#define OPEN_ARROW_SHAPE_A	4.0
#define CLOSED_ARROW_SHAPE_A	ARROW_SHAPE_B

#define LIGHTNING_SHAPE_A_RATIO	10.0
#define LIGHTNING_SHAPE_B_RATIO	8.0

#define LIGHTNING_POINTS	4
#define CORNER_POINTS		3
#define DOUBLE_CORNER_POINTS	4
#define STRAIGHT_POINTS		2


/*
 **********************************************************************************
 *
 * ZnSetLineStyle -- 
 *
 **********************************************************************************
 */
void
ZnSetLineStyle(ZnWInfo	   *wi,
	       ZnLineStyle line_style)
{
  if (wi->render) {
#ifdef GL
    switch (line_style) {
    case ZN_LINE_DASHED :
      glLineStipple(1, 0xF0F0);      
      glEnable(GL_LINE_STIPPLE);
      break;
    case ZN_LINE_MIXED :
      glLineStipple(1, 0x27FF);      
      glEnable(GL_LINE_STIPPLE);
      break;
    case ZN_LINE_DOTTED :
      glLineStipple(1, 0x18C3);      
      glEnable(GL_LINE_STIPPLE);
      break;
    default:
      glDisable(GL_LINE_STIPPLE);
    }
#endif
  }
  else {
    XGCValues		values;
    static const char	dashed[] = { 8 };
    static const char	dotted[] = { 2, 5 };
    static const char	mixed[]  = { 8, 5, 2, 5 };
    
    values.line_style = LineOnOffDash;
    switch (line_style) {
    case ZN_LINE_DASHED :
      XSetDashes(wi->dpy, wi->gc, 0, dashed, 1);
      break;
    case ZN_LINE_MIXED :
      XSetDashes(wi->dpy, wi->gc, 0, mixed, 4);
      break;
    case ZN_LINE_DOTTED :
      XSetDashes(wi->dpy, wi->gc, 0, dotted, 2);
      break;
    default:
      values.line_style = LineSolid;
      break;
    }
    XChangeGC(wi->dpy, wi->gc, GCLineStyle, &values);
  }
}


/*
 **********************************************************************************
 *
 * ZnLineShapePoints --
 *	Compute the points describing the given line shape between point p1 and p2.
 *	If bbox is non null, it is filled with the bounding box of the shape.
 *
 * For the time being this procedure handles straight lines, right and left
 * lightnings, right and left corners, right and left double corners..
 *
 *
 * Here are the parameters for lightnings:
 *
 *                                        *******
 *                                 *******     *
 *                           ******           *
 *                     ******      ******+   *
 *               ******      ******     *   *|
 *         ******      ******          *   * | LIGHTNING_SHAPE_A
 *   ******      ******               *   *  |
 *         ******                    *   *   |
 * ..******.........................+.+.*........................******..
 *   |                             *   *                    ******
 *   |                            *   *               ******      ******
 *   |                           *   *          ******      ******
 *   |                          *   *     ******      ******
 *   |                         *   *******      ******
 *   |                        *          ******
 *   |                       *     ******
 *   |                      ********   
 *   |                         |    | |
 *   |                         |----| | LIGHTNING_SHAPE_B
 *   |                                |
 *   |--------------------------------| LENGTH / 2
 *
 **********************************************************************************
 */
void
ZnLineShapePoints(ZnPoint	*p1,
		  ZnPoint	*p2,
		  ZnDim		line_width,
		  ZnLineShape	shape,
		  ZnBBox	*bbox,
		  ZnList	to_points)
{
  ZnPoint	*points;
  unsigned int	num_points, i;

  /*
   * Compute all line points according to shape.
   */
  if ((shape == ZN_LINE_LEFT_LIGHTNING) ||
      (shape == ZN_LINE_RIGHT_LIGHTNING)) {
    double	alpha, theta;
    double	length, length2;
    double	shape_a, shape_b;
    double	dx, dy;
    double	temp;

    num_points = LIGHTNING_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[3] = *p2;

    dx = p2->x - p1->x;
    dy = p2->y - p1->y;
    length = hypot(dx, dy);
    shape_a = length/LIGHTNING_SHAPE_A_RATIO + line_width/2.0;
    shape_b = length/LIGHTNING_SHAPE_B_RATIO + line_width/2.0;

    if (shape == ZN_LINE_LEFT_LIGHTNING)
      alpha = atan2(shape_a, shape_b);
    else
      alpha = -atan2(shape_a, shape_b);
    length2 = hypot(shape_a, shape_b);
    theta = atan2(-dy, dx);

    dx = p1->x + dx/2;
    dy = p1->y + dy/2;
    temp = cos(theta + alpha) * length2;
    points[1].x = dx + temp;
    points[2].x = dx - temp;
    temp = sin(theta + alpha) * length2;
    points[1].y = dy - temp;
    points[2].y = dy + temp;
  }
  else if (shape == ZN_LINE_LEFT_CORNER ||
	   shape == ZN_LINE_RIGHT_CORNER) {
    num_points = CORNER_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[2] = *p2;

    if (shape == ZN_LINE_LEFT_CORNER) {
      points[1].x = p1->x;
      points[1].y = p2->y;
    }
    else {
      points[1].x = p2->x;
      points[1].y = p1->y;
    }
  }
  else if (shape == ZN_LINE_DOUBLE_LEFT_CORNER ||
	   shape == ZN_LINE_DOUBLE_RIGHT_CORNER) {
    int	dx, dy;

    num_points = DOUBLE_CORNER_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[3] = *p2;

    if (shape == ZN_LINE_DOUBLE_LEFT_CORNER) {
      dy = (int) (p2->y - p1->y);
      points[1].x = p1->x;
      points[2].x = p2->x;
      points[1].y = points[2].y = p1->y + dy/2;
    }
    else {
      dx = (int) (p2->x - p1->x);
      points[1].x = points[2].x = p1->x + dx/2;
      points[1].y = p1->y;
      points[2].y = p2->y;
    }
  }
  else /* if (shape) == ZN_LINE_STRAIGHT) */ {
    num_points = STRAIGHT_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[1] = *p2;
  }

  /*
   * Fill in the bbox, if requested.
   */
  if (bbox) {
    ZnResetBBox(bbox);
    for (i = 0; i < num_points; i++) {
      ZnAddPointToBBox(bbox, points[i].x, points[i].y);
    }
    
    /* Enlarge to take line_width into account. */
    if (line_width > 1) {
      ZnDim lw_2 = (line_width+1)/2;
      
      bbox->orig.x -= lw_2;
      bbox->orig.y -= lw_2;
      bbox->corner.x += lw_2;
      bbox->corner.y += lw_2;
    }
  }
}


/*
 **********************************************************************************
 *
 * ZnDrawLineShape --
 *	Draw a line given the points describing its path. It is designed to work
 *	with GetLineShape albeit it does fairly trivial things. In the future some
 *	shapes might need cooperation between the two and the clients will be ready
 *	for that.
 *
 *
 **********************************************************************************
 */
void
ZnDrawLineShape(ZnWInfo		*wi,
		ZnPoint		*p,
		unsigned int	num_p,
		ZnLineStyle	line_style,
		int		foreground_pixel,
		ZnDim		line_width,
		ZnLineShape	shape __unused)
{
  XPoint	*xpoints;
  unsigned int	i;
  XGCValues	values;

  /*
   * Setup GC.
   */
  ZnSetLineStyle(wi, line_style);
  values.foreground = foreground_pixel;
  values.line_width = (line_width == 1) ? 0 : (int) line_width;
  values.fill_style = FillSolid;
  values.join_style = JoinRound;
  values.cap_style = CapRound;
  XChangeGC(wi->dpy, wi->gc,
	    GCFillStyle|GCLineWidth|GCJoinStyle|GCCapStyle|GCForeground, &values);
  ZnListAssertSize(wi->work_xpts, num_p);
  xpoints = (XPoint *) ZnListArray(wi->work_xpts);
  for (i = 0; i < num_p; i++) {
    xpoints[i].x = (short) p[i].x;
    xpoints[i].y = (short) p[i].y;
  }
  XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, xpoints, (int) num_p, CoordModeOrigin);
}


/*
 **********************************************************************************
 *
 * ZnGetLineEnd --
 *	Compute the points describing the given line end style at point p1 for
 *	the line p1,p2. Point p1 is adjusted to fit the line end.
 *	If bbox is non null, it is filled with the bounding box of the end.
 *
 * For the time being this procedure handles open/filled arrows.
 *
 * Here are the parameters describing arrows.
 *
 *              *  | ARROW_SHAPE_C
 *            **   |
 *          * ***************************
 *        *  *
 *      *   *  +p1                      +p2
 *      | * |*
 *      |   * ***************************
 *      |   | **
 *      |   |   *
 *      |   |   |
 *      |---|   | ARROW_SHAPE_A
 *      |       |
 *      |-------| ARROW_SHAPE_B
 *
 **********************************************************************************
 */
void
ZnGetLineEnd(ZnPoint	*p1,
	     ZnPoint	*p2,
	     ZnDim	line_width,
	     int	cap_style,
	     ZnLineEnd	end_style,
	     ZnPoint	*points)
{
  ZnReal	dx, dy, length, temp, backup;
  ZnReal	frac_height, sin_theta, cos_theta;
  ZnReal	vert_x, vert_y;
  ZnReal	shape_a, shape_b, shape_c;
  
  if (end_style != NULL) {
    shape_a = end_style->shape_a + 0.001;
    shape_b = end_style->shape_b + 0.001;
    shape_c = end_style->shape_c + line_width/2.0 + 0.001;

    frac_height = (line_width/2.0) / shape_c;
    dx = p1->x - p2->x;
    dy = p1->y - p2->y;
    length = hypot(dx, dy);
    if (length == 0) {
      sin_theta = cos_theta = 0.0;
    }
    else {
      sin_theta = dy/length;
      cos_theta = dx/length;
    }

    if (cap_style != CapProjecting) {
      temp = frac_height;
    }
    else {
      temp = line_width / shape_c;
    }
    backup = temp * shape_b + shape_a * (1.0 - temp) / 2.0;
    points[0].x = points[5].x = p1->x + backup * cos_theta;
    points[0].y = points[5].y = p1->y + backup * sin_theta;
    
    vert_x = points[0].x - shape_a*cos_theta;
    vert_y = points[0].y - shape_a*sin_theta;
    temp = shape_c*sin_theta;
    points[1].x = ZnNearestInt(points[0].x - shape_b*cos_theta + temp);
    points[4].x = ZnNearestInt(points[1].x - 2*temp);
    temp = shape_c*cos_theta;
    points[1].y = ZnNearestInt(points[0].y - shape_b*sin_theta - temp);
    points[4].y = ZnNearestInt(points[1].y + 2*temp);
    points[2].x = ZnNearestInt(points[1].x*frac_height + vert_x*(1.0-frac_height));
    points[2].y = ZnNearestInt(points[1].y*frac_height + vert_y*(1.0-frac_height));
    points[3].x = ZnNearestInt(points[4].x*frac_height + vert_x*(1.0-frac_height));
    points[3].y = ZnNearestInt(points[4].y*frac_height + vert_y*(1.0-frac_height));
  }
}

static ZnReal
SegmentPosInRelief(ZnReal		x1,
		     ZnReal		y1,
		     ZnReal		x2,
		     ZnReal		y2,
		     ZnReliefStyle	relief,
		     int		light_angle)
{
  ZnReal	angle, angle_step, origin, position;
  int		num_colors, color_index;
  
  num_colors = ZN_RELIEF_STEPS*2+1;
  angle_step = M_PI / (num_colors-1);
  origin = -(ZnDegRad(light_angle))-(angle_step/2.0);
  if (relief == ZN_RELIEF_SUNKEN) {
    origin += M_PI;
  }

  angle = ZnProjectionToAngle(y1 - y2, x2 - x1) + M_PI - origin;
  while (angle < 0.0) {
    angle += 2*M_PI;
  }
  while (angle > 2*M_PI) {
    angle -= 2*M_PI;
  }	

  color_index = (int) (angle/angle_step);
  if (color_index > num_colors-1) {
    color_index = 2*(num_colors-1)-color_index;
  }
  if ((color_index < 0) || (color_index >= num_colors)) {
    fprintf(stderr, "Color index out of gradient (should not happen).\n");
    if (color_index < 0) {
      color_index = 0;
    }
    if (color_index >= num_colors) {
      color_index = num_colors-1;
    }
  }
  position = 100*color_index/num_colors;
  /*printf("position %g, angle %g(%g), origin %g\n",
	 position,
	 RadianToDegrees(angle),
	 angle,
	 RadianToDegrees(origin));*/
  return position;
}

/*
 * ReliefColorOfSegment --
 * ReliefPixelOfSegment --
 */
static XColor *
ReliefColorOfSegment(ZnReal		x1,
		     ZnReal		y1,
		     ZnReal		x2,
		     ZnReal		y2,
		     ZnReliefStyle	relief,
		     ZnGradient		*gradient,
		     int		light_angle)
{
  return ZnGetGradientColor(gradient,
			    SegmentPosInRelief(x1, y1, x2, y2, relief, light_angle),
			    NULL);
}

static int
ReliefPixelOfSegment(ZnReal		x1,
		     ZnReal		y1,
		     ZnReal		x2,
		     ZnReal		y2,
		     ZnReliefStyle	relief,
		     ZnGradient		*gradient,
		     int		light_angle)
{
  return ZnGetGradientPixel(gradient,
			    SegmentPosInRelief(x1, y1, x2, y2, relief, light_angle));
}


/*
 **********************************************************************************
 *
 * ZnDrawRectangleRelief --
 *	Draw the bevels inside bbox.
 *
 **********************************************************************************
 */
void
ZnDrawRectangleRelief(ZnWInfo		*wi,
		      ZnReliefStyle	relief,
		      ZnGradient	*gradient,
		      XRectangle	*bbox,
		      ZnDim		line_width)
{
  XPoint	bevel[4];
  
  /*
   * If we haven't enough space to draw, exit.
   */
  if ((bbox->width < 2*line_width) || (bbox->height < 2*line_width)) {
    return;
  }
  
  /*
   * Grooves and ridges are drawn with two recursives calls with
   * half the width of the original one.
   */
  if ((relief == ZN_RELIEF_RIDGE) || (relief == ZN_RELIEF_GROOVE)) {
    ZnDim	 new_line_width;
    unsigned int offset;
    XRectangle	 internal_bbox;
    
    new_line_width = line_width/2.0;
    offset = (unsigned) (line_width - new_line_width);
    ZnDrawRectangleRelief(wi,
			  (unsigned) ((relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_SUNKEN:ZN_RELIEF_RAISED),
			  gradient, bbox, new_line_width);
    internal_bbox = *bbox;
    internal_bbox.x += offset;
    internal_bbox.y += offset;
    internal_bbox.width -= offset*2;
    internal_bbox.height -= offset*2;
    ZnDrawRectangleRelief(wi,
			  (unsigned) ((relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_RAISED:ZN_RELIEF_SUNKEN),
			  gradient, &internal_bbox, new_line_width);
    return;
  }

  XSetFillStyle(wi->dpy, wi->gc, FillSolid);
  
  bevel[0].x = bbox->x;
  bevel[0].y = bevel[1].y = bbox->y;
  bevel[1].x = bbox->x + bbox->width;
  bevel[2].y = bevel[3].y = bbox->y + (short) line_width;
  bevel[2].x = bevel[1].x - (short) line_width;
  bevel[3].x = bevel[0].x + (short) line_width;  
  XSetForeground(wi->dpy, wi->gc,
		 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
				      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
				      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);

  bevel[0] = bevel[1];
  bevel[3] = bevel[2];
  bevel[1].y += bbox->height;
  bevel[2].y = bevel[1].y - (short) line_width;
  XSetForeground(wi->dpy, wi->gc,
		 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
				      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
				      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);

  bevel[0] = bevel[1];
  bevel[3] = bevel[2];
  bevel[1].x -= bbox->width;
  bevel[2].x = bevel[1].x + (short) line_width;
  XSetForeground(wi->dpy, wi->gc,
		 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
				      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
				      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);

  bevel[0] = bevel[1];
  bevel[3] = bevel[2];
  bevel[1].x = bbox->x;
  bevel[1].y = bbox->y;
  bevel[2].x = bevel[3].x;
  bevel[2].y = bbox->y + (short) line_width;
  XSetForeground(wi->dpy, wi->gc,
		 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
				      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
				      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);
}


typedef struct {
  ZnWInfo	*wi;
  ZnPoint	*pp;
  ZnPoint	*p0;
  ZnPoint	*p1;
  double	dist;
  ZnBBox	*bbox;
  ZnReliefStyle	relief;
  ZnGradient	*gradient;
  unsigned short alpha;
  ZnBool	smooth;
  int		result;
  int		count;
  ZnBool	toggle;
} PolygonData;

static void
DoPolygon(ZnPoint	*p,
	  unsigned int	num_points,
	  ZnDim		line_width,
	  ZnBool	(*cb)(ZnPoint *bevels, PolygonData *pd),
	  PolygonData	*pd)
{
  int		i;
  unsigned int	processed_points;
  ZnPoint	*p1, *p11=NULL, *p2;
  ZnPoint	pp1, pp2, new_pp1, new_pp2;
  ZnPoint	perp, c, shift1, shift2;
  ZnPoint	bevel_points[4];
  ZnBool	folded, closed, colinear;
  ZnReal	dx, dy;

  if (num_points < 2) {
    return;
  }

  /*
   * If the polygon is closed (last point is the same as first) open it by
   * dropping the last point. The algorithm closes the path automatically.
   * We remember this to decide if we draw the last bevel or not and if we
   * need to generate ends perpendicular to the path..
   */
  closed = False;
  if ((p->x == p[num_points-1].x) && (p->y == p[num_points-1].y)) {
    closed = True;
    num_points--;
  }
  /*printf("num_points=%d(%s)\n", num_points, closed?"closed":"");*/
  
  /*
   * We loop on all vertices of the polygon.
   * At each step we try to compute the corresponding border
   * corner `corner'. Then we build a polygon for the bevel.
   * Things look like this:
   *
   *          bevel[1]     /
   *             *        /
   *		 |       /
   *             |      /
   *         pp1 *    * p[i-1]
   *             |    | bevel[0]
   *             |    |
   *             |    |
   *             |    | bevel[3]
   *             |    | p[i]
   *             |    | p1                 p2
   *         pp2 *    *--------------------*
   *             |
   *             |
   *      corner *----*--------------------*
   *     bevel[2]   new_pp1             new_pp2
   *
   * pp1 and pp2 are the ends of a segment // to p1 p2 at line_width
   * from it. These points are *NOT* necessarily on the perpendicular
   * going through p1 or p2.
   * This loop needs a bootstrap phase of two iterations (i.e we need to
   * process two points). This is why we start at the point before the last
   * and then wrap to the first point.
   * The algorithm discards any duplicate contiguous points.
   * It makes a special case if two consecutives edges are folded:
   *
   *  bevel[1]      pp1            pp2        a bevel[2]
   *    *-----------*--------------*----------*
   *                                           \
   *                                            \
   *     p[i-1]                                  \  bevel[3]
   *       *--------*-------------------------*---* corner
   *    bevel[0]    p2                       p1  /
   *                                            /
   *                                           /
   *      ----------*-----------*-------------*
   *             new_pp1     new_pp2          c
   *
   * In such a case we need to compute a, c, corner from pp1, pp2, new_pp1
   * and new_pp2. We compute the perpendicular to p1,p2 through p1, intersect
   * it with pp1,pp2 to obtain a, intersect it with new_pp1, new_pp2 to
   * obtain c, shift a,c and intersect it with p1,p2 to obtain corner.
   *
   */

  processed_points = 0;
  if (!closed) {
    i = 0;
    p1 = p;
  }
  else {
    i = -2;
    p1 = &p[num_points-2];
  }

  for (p2 = p1+1; i < (int) num_points; i++, p2++) {
    /*
     * When it is time to wrap, do it
     */
    if ((i == -1) || (i == (int) num_points-1)) {
      p2 = p;
    }
    /*
     * Skip over close vertices.
     */
    dx = p2->x - p1->x;
    dy = p2->y - p1->y;
    if ((ABS(dx) < 1.0) && (ABS(dy) < 1.0)) {
      continue;
    }

    ZnShiftLine(p1, p2, line_width, &new_pp1, &new_pp2);
    bevel_points[3] = *p1;
    folded = False;
    colinear = False;
    /*
     * The first two cases are for `open' polygons. We compute
     * a bevel closure that is perpendicular to the path.
     */
    if ((processed_points == 0) && !closed) {
      perp.x = p1->x + (p2->y - p1->y);
      perp.y = p1->y - (p2->x - p1->x);
      ZnIntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]);
    }
    else if ((processed_points == num_points-1) && !closed) {
      perp.x = p1->x + (p11->y - p1->y);
      perp.y = p1->y - (p11->x - p1->x);
      ZnIntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]);      
    }
    else if (processed_points >= 1) {
      ZnReal	dotp, dist, odx, ody;
      
      /*
       * The dot product of the two faces tell if the are
       * folded or colinear. The
       */
      odx = p11->x - p1->x;
      ody = p11->y - p1->y;
      dotp = odx*dx + ody*dy;
      dist = ZnLineToPointDist(p11, p2, p1, NULL);
      if ((dist < 4.0) && (dotp <= 0)) {
	perp.x = p1->x + (p2->y - p1->y);
	perp.y = p1->y - (p2->x - p1->x);
	ZnIntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]);
	colinear = True;
      }
      else {
	folded = !ZnIntersectLines(&new_pp1, &new_pp2, &pp1, &pp2, &bevel_points[2]);
	/*printf("new_pp1 %g@%g, new_pp2 %g@%g, pp1 %g@%g, pp2 %g@%g, inter %g@%g\n",
	       new_pp1.x, new_pp1.y, new_pp2.x, new_pp2.y,
	       pp1.x, pp1.y, pp2.x, pp2.y,
	       bevel_points[2].x, bevel_points[2].y);*/
	folded = folded && (dotp < 0);
	if (folded) {
	  /*printf("DoPolygonRelief: folded edges detected, %g@%g, %g@%g, %g@%g, %g@%g\n",
		 pp1.x, pp1.y, pp2.x, pp2.y, new_pp1.x, new_pp1.y,
		 new_pp2.x, new_pp2.y);*/
	  perp.x = p1->x + (p2->y - p1->y);
	  perp.y = p1->y - (p2->x - p1->x);
	  ZnIntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]);
	  ZnIntersectLines(p1, &perp, &new_pp1, &new_pp2, &c);
	  ZnShiftLine(p1, &perp, line_width, &shift1, &shift2);
	  ZnIntersectLines(p1, p2, &shift1, &shift2, &bevel_points[3]);
	}
      }
    }

    if ((processed_points >= 2) || (!closed && (processed_points == 1))) {
      if ((processed_points == num_points-1) && !closed) {
	pd->p0 = pd->p1 = NULL;
      }
      else {
	pd->p0 = p1;
	pd->p1 = p2;
      }
      if ((*cb)(bevel_points, pd)) {
	return;
      }
    }
    
    p11 = p1;
    p1 = p2;
    pp1 = new_pp1;
    pp2 = new_pp2;
    bevel_points[0] = bevel_points[3];
    if (folded) {
      bevel_points[1] = c;
    }
    else if ((processed_points >= 1) || !closed) {
      bevel_points[1] = bevel_points[2];
    }

    processed_points++;
  }
}


/*
 **********************************************************************************
 *
 * ZnGetPolygonReliefBBox --
 *	Returns the bevelled polygon bounding box.
 *
 **********************************************************************************
 */
static ZnBool
PolygonBBoxCB(ZnPoint		*bevels,
	      PolygonData	*pd)
{
  int	 i;

  for (i = 0; i < 4; i++) {
    ZnAddPointToBBox(pd->bbox, bevels[i].x, bevels[i].y);
  }
  return 0;
}

void
ZnGetPolygonReliefBBox(ZnPoint		*points,
		       unsigned int	num_points,
		       ZnDim		line_width,
		       ZnBBox		*bbox)
{
  PolygonData	pd;

  pd.bbox = bbox;
  ZnResetBBox(bbox);
  DoPolygon(points, num_points, line_width, PolygonBBoxCB, &pd);
}


/*
 **********************************************************************************
 *
 * ZnPolygonReliefInBBox --
 *	Returns (-1) if the relief is entirely outside the bbox, (1) if it is
 *	entirely inside or (0) if in between
 *
 **********************************************************************************
 */
static ZnBool
PolygonInBBoxCB(ZnPoint		*bevels,
		PolygonData	*pd)
{
  if (pd->count == 0) {
    pd->count++;
    pd->result = ZnPolygonInBBox(bevels, 4, pd->bbox, NULL);
    if (pd->result == 0) {
      return 1;
    }
  }
  else {
    if (ZnPolygonInBBox(bevels, 4, pd->bbox, NULL) != pd->result) {
      pd->result = 0;
      return 1;
    }
  }
  return 0;
}

int
ZnPolygonReliefInBBox(ZnPoint		*points,
		      unsigned int	num_points,
		      ZnDim		line_width,
		      ZnBBox		*area)
{
  PolygonData	pd;

  pd.bbox = area;
  pd.count = 0;

  DoPolygon(points, num_points, line_width, PolygonInBBoxCB, &pd);

  return pd.result;
}


/*
 **********************************************************************************
 *
 * ZnPolygonReliefToPointDist --
 *	Returns the distance between the given point and
 *	the bevelled polygon.
 *
 **********************************************************************************
 */
static ZnBool
PolygonDistCB(ZnPoint		*bevels,
	      PolygonData	*pd)
{
  double	new_dist;

  new_dist = ZnPolygonToPointDist(bevels, 4, pd->pp);
  if (new_dist < 0.0) {
    new_dist = 0.0;
  }
  if (new_dist < pd->dist) {
    pd->dist = new_dist;
  }
  return 0;
}

double
ZnPolygonReliefToPointDist(ZnPoint	*points,
			   unsigned int	num_points,
			   ZnDim	line_width,
			   ZnPoint	*pp)
{
  PolygonData	pd;

  pd.dist = 1.0e40;
  pd.pp = pp;
  DoPolygon(points, num_points, line_width, PolygonDistCB, &pd);

  return pd.dist;
}


/*
 **********************************************************************************
 *
 * ZnDrawPolygonRelief --
 *	Draw the bevels around path.
 *
 **********************************************************************************
 */
static ZnBool
PolygonDrawCB(ZnPoint		*bevels,
	      PolygonData	*pd)
{
  ZnWInfo	*wi = pd->wi;
  XPoint	bevel_xpoints[5];
  XGCValues	values;
  int		j;

  values.foreground = ReliefPixelOfSegment(bevels[0].x, bevels[0].y,
					   bevels[3].x, bevels[3].y,
					   pd->relief, pd->gradient,
					   pd->wi->light_angle);

  values.fill_style = FillSolid;
  XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &values);
	
  for (j = 0; j < 4; j++) {
    bevel_xpoints[j].x = ZnNearestInt(bevels[j].x);
    bevel_xpoints[j].y = ZnNearestInt(bevels[j].y);
  }

  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel_xpoints, 4,
	       Convex, CoordModeOrigin);

  return 0;
}

void
ZnDrawPolygonRelief(ZnWInfo		*wi,
		    ZnReliefStyle	relief,
		    ZnGradient		*gradient,
		    ZnPoint		*points,
		    unsigned int	num_points,
		    ZnDim		line_width)
{
  PolygonData	pd;

  pd.wi = wi;
  pd.gradient = gradient;

  /*
   * Grooves and ridges are drawn with two calls. The first
   * with the original width, the second with half the width.
   */
  if ((relief == ZN_RELIEF_RIDGE) || (relief == ZN_RELIEF_GROOVE)) {
    pd.relief = (relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_RAISED:ZN_RELIEF_SUNKEN;
    DoPolygon(points, num_points, line_width, PolygonDrawCB, &pd);
    pd.relief = (relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_SUNKEN:ZN_RELIEF_RAISED;
    DoPolygon(points, num_points, line_width/2, PolygonDrawCB, &pd);
  }
  else {
    pd.relief = relief;
    DoPolygon(points, num_points, line_width, PolygonDrawCB, &pd);
  }
}

/*
 **********************************************************************************
 *
 * ZnRenderPolygonRelief --
 *	Draw the bevels around path using alpha enabled rendering.
 *
 **********************************************************************************
 */
#ifdef GL
static ZnBool
PolygonRenderCB(ZnPoint		*bevels,
		PolygonData	*pd)
{
  int		i;
  ZnPoint	p[6];
  XColor	*c[8];
  XColor	*color = ZnGetGradientColor(pd->gradient, 51.0, NULL);
  ZnReliefStyle	relief, int_relief;
  ZnBool	two_faces, round, rule;

  rule = pd->relief & ZN_RELIEF_RULE;
  round = pd->relief & ZN_RELIEF_ROUND;
  two_faces = pd->relief & ZN_RELIEF_TWO_FACES;
  relief = pd->relief & ZN_RELIEF_MASK;
  for (i = 0; i < 4; i++) {
    p[i].x = ZnNearestInt(bevels[i].x);
    p[i].y = ZnNearestInt(bevels[i].y);
  }

  if (two_faces) {
    p[4].x = (p[0].x+p[1].x)/2;
    p[4].y = (p[0].y+p[1].y)/2;
    p[5].x = (p[2].x+p[3].x)/2;
    p[5].y = (p[2].y+p[3].y)/2;

    if (relief == ZN_RELIEF_SUNKEN) {
      int_relief = ZN_RELIEF_RAISED;
    }
    else {
      int_relief = ZN_RELIEF_SUNKEN;
    }
    c[0]=c[1]=c[2]=c[3] = ReliefColorOfSegment(bevels[0].x, bevels[0].y,
					       bevels[3].x, bevels[3].y,
					       relief, pd->gradient,
					       pd->wi->light_angle);
    c[4]=c[5]=c[6]=c[7] = ReliefColorOfSegment(bevels[0].x, bevels[0].y,
					       bevels[3].x, bevels[3].y,
					       int_relief, pd->gradient,
					       pd->wi->light_angle);
    if (pd->smooth && pd->p0) {
      c[2]=c[3] = ReliefColorOfSegment(pd->p0->x, pd->p0->y,
				       pd->p1->x, pd->p1->y,
				       relief, pd->gradient,
				       pd->wi->light_angle);
      c[6]=c[7] = ReliefColorOfSegment(pd->p0->x, pd->p0->y,
				       pd->p1->x, pd->p1->y,
				       int_relief, pd->gradient,
				       pd->wi->light_angle);
    }
    if (round) {
      if (!rule) {
	c[0]=c[3]=c[5]=c[6]=color;
      }
      else {
	c[1]=c[2]=c[4]=c[7]=color;
      }
    }
    glBegin(GL_QUADS);
    glColor4us(c[0]->red, c[0]->green, c[0]->blue, pd->alpha);
    glVertex2d(p[0].x, p[0].y);

    glColor4us(c[1]->red, c[1]->green, c[1]->blue, pd->alpha);
    glVertex2d(p[4].x, p[4].y);

    glColor4us(c[2]->red, c[2]->green, c[2]->blue, pd->alpha);
    glVertex2d(p[5].x, p[5].y);

    glColor4us(c[3]->red, c[3]->green, c[3]->blue, pd->alpha);
    glVertex2d(p[3].x, p[3].y);

    glColor4us(c[4]->red, c[4]->green, c[4]->blue, pd->alpha);
    glVertex2d(p[4].x, p[4].y);

    glColor4us(c[5]->red, c[5]->green, c[5]->blue, pd->alpha);
    glVertex2d(p[1].x, p[1].y);

    glColor4us(c[6]->red, c[6]->green, c[6]->blue, pd->alpha);
    glVertex2d(p[2].x, p[2].y);

    glColor4us(c[7]->red, c[7]->green, c[7]->blue, pd->alpha);
    glVertex2d(p[5].x, p[5].y);
    glEnd();
  }
  else { /* Single face */
    c[0]=c[1]=c[2]=c[3] = ReliefColorOfSegment(bevels[0].x, bevels[0].y,
					       bevels[3].x, bevels[3].y,
					       relief, pd->gradient,
					       pd->wi->light_angle);
    if (pd->smooth && pd->p0) {
      c[2]=c[3] = ReliefColorOfSegment(pd->p0->x, pd->p0->y,
				       pd->p1->x, pd->p1->y,
				       relief, pd->gradient,
				       pd->wi->light_angle);
    }
    if (round) {
      c[1]=c[2] = color;
    }
    glBegin(GL_QUADS);
    glColor4us(c[0]->red, c[0]->green, c[0]->blue, pd->alpha);
    glVertex2d(p[0].x, p[0].y);
    glColor4us(c[1]->red, c[1]->green, c[1]->blue, pd->alpha);
    glVertex2d(p[1].x, p[1].y);
    glColor4us(c[2]->red, c[2]->green, c[2]->blue, pd->alpha);
    glVertex2d(p[2].x, p[2].y);
    glColor4us(c[3]->red, c[3]->green, c[3]->blue, pd->alpha);
    glVertex2d(p[3].x, p[3].y);
    glEnd();
  }

  return 0;
}

void
ZnRenderPolygonRelief(ZnWInfo		*wi,
		      ZnReliefStyle	relief,
		      ZnGradient	*gradient,
		      ZnBool		smooth,
		      ZnPoint		*points,
		      unsigned int	num_points,
		      ZnDim		line_width)
{
  PolygonData	pd;

  pd.wi = wi;
  pd.gradient = gradient;
  ZnGetGradientColor(gradient, 0.0, &pd.alpha);
  pd.alpha = ZnComposeAlpha(pd.alpha, wi->alpha);
  pd.smooth = smooth;
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);	  
  pd.relief = relief;
  pd.count = 0;

  DoPolygon(points, num_points, line_width, PolygonRenderCB, &pd);
}

void
ZnRenderPolyline(ZnWInfo	*wi,
		 ZnPoint	*points,
		 unsigned int	num_points,
		 ZnDim		line_width,
		 ZnLineStyle	line_style,
		 int		cap_style,
		 int		join_style __unused,
		 ZnLineEnd	first_end,
		 ZnLineEnd	last_end,
		 ZnGradient	*gradient)
{
  int		num_clips = ZnListSize(wi->clip_stack);
  ZnPoint	end_points[ZN_LINE_END_POINTS];
  ZnBool	need_rcaps, thin, closed, transparent;
  int		pass, num_passes, i, k, m;
  ZnPoint	c1, c2;    
  XColor	*color;
  unsigned short alpha;
  ZnGLContextEntry *ce = ZnGetGLContext(wi->dpy);

  /*
   * The code below draws curves thiner than the min
   * of GL_SMOOTH_LINE_WIDTH_RANGE and GL_SMOOTH_POINT_SIZE_RANGE
   * with a mix of anti-aliased lines and points. The curves that
   * are thicker are drawn using regular polygons.
   * BUG: The joints are drawn only rounded.
   * The caps can be either round or butt (but not projecting).
   */
  thin = ((line_width <= ce->max_line_width) &&
	  (line_width <= ce->max_point_width));
  closed = (points->x == points[num_points-1].x) && (points->y == points[num_points-1].y);
  color = ZnGetGradientColor(gradient, 0.0, &alpha);
  alpha = ZnComposeAlpha(alpha, wi->alpha);
  glColor4us(color->red, color->green, color->blue, alpha);
  ZnSetLineStyle(wi, line_style);
  glLineWidth((GLfloat) line_width);
  /*
   * Do not use joints under this transparency value.
   */
  transparent = alpha < (65535 * 0.8);
  if (thin && transparent) {
    /*
     * This makes a special case for transparent lines.
     * In this case we need to avoid drawing twice a
     * single pixel. To achieve this we use the stencil
     * buffer to protect already drawn pixels, unfortunately
     * using antialiasing write in the stencil even if
     * the pixel area is not fully covered resulting in
     * a crack that can't be covered by points later on.
     * To handle this case we need to disable the stencil
     * which in turn result in erroneous alpha coverage.
     *
     * We have chosen to drawn transparent lines with a
     * correct coverage but NOT antialiased.
     */
    glPointSize((GLfloat)(line_width>1.0?line_width-1:line_width));
    glDisable(GL_LINE_SMOOTH);
  }
  else {
    glPointSize((GLfloat)(line_width>1.0?line_width-1:line_width));
  }

  num_passes = 1;
  if (transparent) {
    num_passes = 2;
  }
    
  for (pass = 0; pass < num_passes; pass++) {
    if (transparent) {
      if (pass == 0) {
	ZnGlStartClip(num_clips, True);
      }
      else {
	ZnGlRestoreStencil(num_clips, False);
      }
    }
    if (first_end) {
      ZnGetLineEnd(&points[0], &points[1], line_width, cap_style,
		   first_end, end_points);
      glBegin(GL_TRIANGLE_FAN);
      for (m = 0; m < ZN_LINE_END_POINTS; m++) {
	glVertex2d(end_points[m].x, end_points[m].y);
      }
      glEnd();
    }
    if (last_end) {
      ZnGetLineEnd(&points[num_points-1], &points[num_points-2],
		   line_width, cap_style, last_end, end_points);
      glBegin(GL_TRIANGLE_FAN);
      for (m = 0; m < ZN_LINE_END_POINTS; m++) {
	glVertex2d(end_points[m].x, end_points[m].y);
      }
      glEnd();
    }
    if (thin) {
      glBegin(GL_LINE_STRIP);
      for (i = 0; i < (int) num_points; i++) {
	glVertex2d(points[i].x, points[i].y);
      }
      glEnd();
    }
    else {
      glBegin(GL_QUADS);
      for (i = 0; i < (int) num_points-1; i++) {
	ZnGetButtPoints(&points[i+1], &points[i], line_width, False, &c1, &c2);
	glVertex2d(c1.x, c1.y);
	glVertex2d(c2.x, c2.y);
	ZnGetButtPoints(&points[i], &points[i+1], line_width, False, &c1, &c2);
	glVertex2d(c1.x, c1.y);
	glVertex2d(c2.x, c2.y);
      }
      glEnd();
    }

    /* if (pass == 0) {
      ZnGlRenderClipped();
    }
    else {
      ZnGlEndClip(num_clips);
      break;
    }*/
    need_rcaps = ((line_width > 1) && (cap_style == CapRound));
    i = 0;
    k = num_points;
    if (closed) {
      k--;
    }
    if (!need_rcaps || first_end) {
      i++;
    }
    if ((!need_rcaps && !closed) || last_end) {
      k--;
    }

    if (thin) {
      glBegin(GL_POINTS);
      for ( ; i < k; i++) {
	glVertex2d(points[i].x, points[i].y);
      }
      glEnd();
    }
    else {
      int	num_cpoints;
      ZnReal	lw_2 = line_width / 2.0;
      ZnPoint	*cpoints = ZnGetCirclePoints(3, ZN_CIRCLE_COARSE,
					     0.0, 2*M_PI, &num_cpoints, NULL);
      
      for ( ; i < k; i++) {
	glBegin(GL_TRIANGLE_FAN);
	glVertex2d(points[i].x, points[i].y);
	for (m = 0; m < num_cpoints; m++) {
	  glVertex2d(points[i].x + cpoints[m].x*lw_2,
		     points[i].y + cpoints[m].y*lw_2);
	}
	glEnd();
      }
    }
  }
  
  ZnGlEndClip(num_clips);
  if (thin) {
    glEnable(GL_LINE_SMOOTH);
  }
}


void
ZnRenderIcon(ZnWInfo	*wi,
	     ZnImage	image,
	     ZnGradient	*gradient,
	     ZnPoint	*origin,
	     ZnBool	modulate)
{
  ZnPoint	p[4];
  int		width, height;

  ZnSizeOfImage(image, &width, &height);
  p[0] = *origin;
  p[1].x = origin->x;
  p[1].y = origin->y + height;
  p[2].x = origin->x + width;
  p[2].y = p[1].y;
  p[3].x = p[2].x;
  p[3].y = origin->y;
  ZnRenderImage(wi, image, gradient, p, modulate);
}


void
ZnRenderImage(ZnWInfo	 *wi,
	      ZnImage	 image,
	      ZnGradient *gradient,
	      ZnPoint	 *quad,
	      ZnBool	 modulate)
{
  XColor	*color;
  unsigned short alpha;
  ZnReal	t, s;
  GLuint	texobj;
  
  color = ZnGetGradientColor(gradient, 0.0, &alpha);
  alpha = ZnComposeAlpha(alpha, wi->alpha);
  texobj = ZnImageTex(image, &t, &s);
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, texobj);
  if (modulate) {
    glColor4us(color->red, color->green, color->blue, alpha);
  }
  else {
    glColor4us(65535, 65535, 65535, alpha);
  }
  glBegin(GL_QUADS);
  glTexCoord2d(0.0, 0.0);
  glVertex2d(quad[0].x, quad[0].y);
  glTexCoord2d(0.0, t);
  glVertex2d(quad[1].x, quad[1].y);
  glTexCoord2d(s, t);
  glVertex2d(quad[2].x, quad[2].y);
  glTexCoord2d(s, 0.0);
  glVertex2d(quad[3].x, quad[3].y);
  glEnd();
  glDisable(GL_TEXTURE_2D);
}

void
ZnRenderTile(ZnWInfo	*wi,
	     ZnImage	tile,
	     ZnGradient	*gradient,
	     void	(*cb)(void *),
	     void	*closure,
	     ZnPoint	*quad) /* Right now it's a ZnBBox */
{
  ZnReal	x, y, nx, ny, lx, ly, s, t, tiles, tilet;
  int		width, height, num_clips = ZnListSize(wi->clip_stack);
  unsigned short alpha;
  GLuint	texobj;
  XColor	*color;

  if (gradient) {
    color = ZnGetGradientColor(gradient, 0.0, &alpha);
    alpha = ZnComposeAlpha(alpha, wi->alpha);
  }
  else {
    color = NULL;
    alpha = ZnComposeAlpha(100, wi->alpha);
  }
  
  if (cb) {
    /*
     * Setup the stencil buffer with the shape to be drawn.
     */
    ZnGlStartClip(num_clips, False);

    (*cb)(closure);  
    ZnGlRestoreStencil(num_clips, True);
  }
  
  /*
   * Then texture map the quad through the shape.
   * The rectangle is drawn using quads, each
   * quad matching the size of the texture tile.
   */
  ZnSizeOfImage(tile, &width, &height);
  texobj = ZnImageTex(tile, &tilet, &tiles);
  glEnable(GL_TEXTURE_2D);
  if (color && ZnImageIsBitmap(tile)) {
    glColor4us(color->red, color->green, color->blue, alpha);
  }
  else {
    glColor4us(65535, 65535, 65535, alpha);
  }
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glBindTexture(GL_TEXTURE_2D, texobj);
  
  y = quad[0].y;
  lx = quad[1].x;
  ly = quad[1].y;
  glBegin(GL_QUADS);
  do {
    x = quad[0].x;
    t = 1.0;
    ny = y + height;
    if (ny > ly) {
      ny = ly;
      t = (ly - y) / (ZnReal) height;
    }
    t *= tilet;
    do {
      s = 1.0;
      nx = x + width;
      if (nx > lx) {
	nx = lx;
	s = (lx - x) / (ZnReal) width;
      }
      s *= tiles;
      glTexCoord2d(0.0, 0.0);
      glVertex2d(x, y);
      glTexCoord2d(0.0, t);
      glVertex2d(x, ny);
      glTexCoord2d(s, t);
      glVertex2d(nx, ny);
      glTexCoord2d(s, 0.0);
      glVertex2d(nx, y);
      x = nx;
    }
    while (x != lx);
    y = ny;
  }
  while (y != ly);
  glEnd();

  if (cb) {
    ZnGlEndClip(num_clips);
  }
  glDisable(GL_TEXTURE_2D);
}


static void
ComputeAxialGradient(ZnWInfo	*wi,
		     ZnPoly	*shape,
		     ZnReal	angle,
		     ZnPoint	*grad_geo)
{
  ZnTransfo	*transfo1, *transfo2;
  ZnContour	*c;
  ZnBBox	bbox;
  ZnPoint	*points, p[4];
  unsigned int	i;
  
  transfo1 = ZnTransfoNew();
  transfo2 = ZnTransfoNew();
  ZnRotateDeg(transfo1, angle);
  ZnRotateDeg(transfo2, -angle);
  c = shape->contours;
  ZnResetBBox(&bbox);
  for (i = 0; i < shape->num_contours; i++, c++) {
    ZnListAssertSize(wi->work_pts, c->num_points);
    points = ZnListArray(wi->work_pts);
    ZnTransformPoints(transfo1, c->points, points, c->num_points);
    ZnAddPointsToBBox(&bbox, points, c->num_points);
  }
  bbox.orig.x--;
  bbox.orig.y--;
  bbox.corner.x++;
  bbox.corner.y++;
  p[0] = bbox.orig;
  p[2] = bbox.corner;
  p[1].x = p[2].x;
  p[1].y = p[0].y;
  p[3].x = p[0].x;
  p[3].y = p[2].y;
  ZnTransfoSetIdentity(transfo1);
  ZnTransfoCompose(transfo1, transfo2, wi->current_transfo);
  ZnTransformPoints(transfo1, p, grad_geo, 4);
  ZnTransfoFree(transfo1);
  ZnTransfoFree(transfo2);
}

static void
ComputeCircularGradient(ZnWInfo		*wi,
			ZnPoly		*shape,
			ZnBool		oval,
			ZnPoint		*focal_pp, /* in percent of bbox */
			ZnReal		angle,
			ZnPoint		*grad_geo)
{
  ZnReal	dist, new, x, y, ff;
  ZnBBox	bbox;
  ZnContour	*c;
  ZnPoint	offset, radius, focal_point;
  ZnPoint	*points;
  ZnTransfo	t1;
  unsigned int	i, j;

  /*
   * Compute the shape bbox (which should be in the item space).
   */
  ZnResetBBox(&bbox);
  c = shape->contours;
  dist = 0.0;
  for (j = 0; j < shape->num_contours; j++, c++) {
    ZnAddPointsToBBox(&bbox, c->points, c->num_points); 
  }

  /*
   * Find the gradient focal point in the item space.
   * The excursion of the focal point outside the item
   * bbox is clamped to avoid distorsions that take
   * place due to the rather simple algorithm used to
   * compute the maximum radius of the gradient.
   */
  focal_pp->x = fmod(focal_pp->x, 500.0);
  focal_pp->y = fmod(focal_pp->y, 500.0);
  offset.x = focal_pp->x * (bbox.corner.x-bbox.orig.x)/100.0;
  offset.y = focal_pp->y * (bbox.corner.y-bbox.orig.y)/100.0;
  focal_point.x = (bbox.corner.x+bbox.orig.x)/2 + offset.x; 
  focal_point.y = (bbox.corner.y+bbox.orig.y)/2 + offset.y; 

  /*
   * Find the max distance from the focal point.
   */
  if (oval) {
    /*
     * radius.x and radius.y are the shape radiuses.
     * ff is the distance from the bbox center to
     * the focal point.
     */
    radius.x = (bbox.corner.x-bbox.orig.x)/2;
    radius.y = (bbox.corner.y-bbox.orig.y)/2;
    ff = sqrt(offset.x*offset.x + offset.y*offset.y);
    /*
     * Compute the farthest point from the focal point 
     * on a unit circle, then map it to the oval and
     * compute the distance between the two points.
     */
    if (ff > PRECISION_LIMIT) {
      x = offset.x/ff;
      y = offset.y/ff;
      x *= radius.x;
      y *= radius.y;
      x = x + offset.x;
      y = y + offset.y;
    }
    else {
      x = 0;
      y = MAX(radius.x, radius.y);
    }
    dist = x*x + y*y;
  }
  else {
    /*
     * Use the given shape
     */
    c = shape->contours;
    for (j = 0; j < shape->num_contours; j++, c++) {
      for (i = 0, points = c->points; i < c->num_points; i++, points++) {
	x = points->x - focal_point.x;
	y = points->y - focal_point.y;
	new = x*x+y*y;
	if (new > dist) {
	  dist = new;
	}
      }
    }
  }
  
  /*
   * Create a transform to map a unit circle to another one that
   * could fill the item when centered at the focal point.
   */
  dist = sqrt(dist); /* Max radius plus a fuzz factor */
  ZnTransfoSetIdentity(&t1);
  ZnScale(&t1, dist, dist);
  ZnRotateDeg(&t1, -angle);

  /*
   * Then, center the oval on the focal point.
   */
  ZnTranslate(&t1, focal_point.x, focal_point.y, False);
  /*
   * Last, compose with the current transform.
   */
  ZnTransfoCompose((ZnTransfo *) grad_geo, &t1, wi->current_transfo);
}

static void
ComputePathGradient(ZnWInfo	*wi,
		    ZnPoly	*shape,
		    ZnPoint	*focal_pp, /* in percent of the bbox */
		    ZnPoint	*grad_geo)
{
  ZnBBox	bbox;
  ZnContour	*c;
  ZnPoint	focal_point;
  unsigned int	j;

  /*
   * Compute the shape bbox (which should be in the item space).
   */
  ZnResetBBox(&bbox);
  c = shape->contours;
  for (j = 0; j < shape->num_contours; j++, c++) {
    ZnAddPointsToBBox(&bbox, c->points, c->num_points); 
  }

  /*
   * Find the gradient center in the item space.
   */
  focal_point.x = (bbox.corner.x+bbox.orig.x)/2 + focal_pp->x * (bbox.corner.x-bbox.orig.x)/100.0;
  focal_point.y = (bbox.corner.y+bbox.orig.y)/2 + focal_pp->y * (bbox.corner.y-bbox.orig.y)/100.0;
  /*
   * Then convert it to device space.
   */
  ZnTransformPoint(wi->current_transfo, &focal_point, &grad_geo[0]);
}

void
ZnComputeGradient(ZnGradient	*grad,
		  ZnWInfo	*wi,
		  ZnPoly	*shape,
		  ZnPoint	*grad_geo)
{
  switch (grad->type) {
  case ZN_AXIAL_GRADIENT:
    ComputeAxialGradient(wi, shape, grad->angle, grad_geo);
    break;
  case ZN_RADIAL_GRADIENT:
  case ZN_CONICAL_GRADIENT:
    ComputeCircularGradient(wi, shape, False, &grad->p, grad->angle, grad_geo);
    break;
  case ZN_PATH_GRADIENT:
    ComputePathGradient(wi, shape, &grad->p, grad_geo);
    break;
  }
}

void
ZnRenderGradient(ZnWInfo	*wi,
		 ZnGradient	*gradient,	/* The gradient to be drawn (static
						 * parameters). */
		 void		(*cb)(void *),	/* A callback called to clip the shape
						 * containing the gradient. */
		 void		*closure,	/* The callback parameter. */
		 ZnPoint	*quad,		/* The gradient geometric parameters
						 * (dynamic). */
		 ZnPoly		*poly		/* Used only by ZN_PATH_GRADIENT */
		 )
{
  unsigned short alpha, alpha2;
  int		angle;
  unsigned int	i, j;
  int		type = gradient->type;
  XColor	*color;
  ZnPoint	dposa, dposb, dposc, dposd;
  ZnPoint	p, dcontrol;
  ZnReal	npos, pos, control;
  unsigned int	num_clips = ZnListSize(wi->clip_stack);
  ZnPoint	iquad[4];
  
  if (!cb && (type == ZN_AXIAL_GRADIENT)) { /* Render an aligned
					     * axial gradient in the quad */
    angle = gradient->angle;
    /*
     * Adjust the quad for 90 180 and 270 degrees axial
     * gradients. Other angles not supported.
     */
    switch (angle) {
    case 90:
      iquad[0] = quad[3];
      iquad[3] = quad[2];
      iquad[2] = quad[1];
      iquad[1] = quad[0];
      quad = iquad;
      break;
    case 180:
      iquad[0] = quad[2];
      iquad[3] = quad[1];
      iquad[2] = quad[0];
      iquad[1] = quad[3];
      quad = iquad;
      break;
    case 270:
      iquad[0] = quad[1];
      iquad[3] = quad[0];
      iquad[2] = quad[3];
      iquad[1] = quad[2];      
      quad = iquad;
      break;
    }
  }

  if (cb) {
    /*
     * Draw the gradient shape in the stencil using the provided
     * callback (clipping).
     */
    ZnGlStartClip(num_clips, False);
    (*cb)(closure);
    ZnGlRestoreStencil(num_clips, True);
  }

  if (type == ZN_AXIAL_GRADIENT) {
    /*
     * Then fill the axial gradient using the provided
     * quad and colors. The stencil will be restored
     * to its previous state in the process.
     */
    glBegin(GL_QUAD_STRIP);
    for (i = 0; i < gradient->num_actual_colors; i++) {
      color = gradient->actual_colors[i].rgb;
      alpha = ZnComposeAlpha(gradient->actual_colors[i].alpha, wi->alpha);
      glColor4us(color->red, color->green, color->blue, alpha);

      pos = gradient->actual_colors[i].position;
      control = gradient->actual_colors[i].control;
      dposa.x = (quad[1].x - quad[0].x)*pos/100.0;
      dposa.y = (quad[1].y - quad[0].y)*pos/100.0;
      p.x = quad[0].x + dposa.x;
      p.y = quad[0].y + dposa.y;
      glVertex2d(p.x, p.y);
      
      dposb.x = (quad[2].x - quad[3].x)*pos/100.0;
      dposb.y = (quad[2].y - quad[3].y)*pos/100.0;      
      p.x = quad[3].x + dposb.x;
      p.y = quad[3].y + dposb.y;
      glVertex2d(p.x, p.y);
      
      if ((control != 50.0) && (i != gradient->num_actual_colors-1)) {
	color = gradient->actual_colors[i].mid_rgb;
	alpha = ZnComposeAlpha(gradient->actual_colors[i].mid_alpha, wi->alpha);
	glColor4us(color->red, color->green, color->blue, alpha);
	
	npos = gradient->actual_colors[i+1].position;
	dposc.x = (quad[1].x - quad[0].x)*npos/100.0;
	dposc.y = (quad[1].y - quad[0].y)*npos/100.0;
	dcontrol.x = (dposc.x - dposa.x)*control/100.0;
	dcontrol.y = (dposc.y - dposa.y)*control/100.0;
	p.x = quad[0].x + dposa.x + dcontrol.x;
	p.y = quad[0].y + dposa.y + dcontrol.y;
	glVertex2d(p.x, p.y);
	
	dposd.x = (quad[2].x - quad[3].x)*npos/100.0;
	dposd.y = (quad[2].y - quad[3].y)*npos/100.0;
	dcontrol.x = (dposd.x - dposb.x)*control/100.0;
	dcontrol.y = (dposd.y - dposb.y)*control/100.0;      
	p.x = quad[3].x + dposb.x + dcontrol.x;
	p.y = quad[3].y + dposb.y + dcontrol.y;
	glVertex2d(p.x, p.y);
      }
      
    }
    glEnd();
  }
  else if (type == ZN_RADIAL_GRADIENT) {
    ZnReal	 x, y, position, position2, position3;
    unsigned int num_p;
    ZnPoint	 *genarc, *tarc, p, focalp;
    XColor	 *color2;

    genarc = ZnGetCirclePoints(3, ZN_CIRCLE_FINE, 0.0, 2*M_PI, &num_p, NULL);
    ZnListAssertSize(wi->work_pts, num_p);
    tarc = ZnListArray(wi->work_pts);
    ZnTransformPoints((ZnTransfo *) quad, genarc, tarc, num_p);
    p.x = p.y = 0;
    ZnTransformPoint((ZnTransfo *) quad, &p, &focalp);
    
    position = 0.0;
    color = gradient->actual_colors[0].rgb;
    alpha = ZnComposeAlpha(gradient->actual_colors[0].alpha, wi->alpha);
    control = gradient->actual_colors[0].control;
    for (j = 1; j < gradient->num_actual_colors; j++) {
      position2 = gradient->actual_colors[j].position/100.0;
      if ((control != 50) && (j != gradient->num_actual_colors-1)) {
	glBegin(GL_QUAD_STRIP);
	color2 = gradient->actual_colors[j-1].mid_rgb;
	alpha2 = ZnComposeAlpha(gradient->actual_colors[j-1].mid_alpha, wi->alpha);
	position3 = position + (position2-position)*control/100.0;
	for (i = 0; i < num_p; i++) {
	  x = focalp.x + (tarc[i].x-focalp.x) * position;
	  y = focalp.y + (tarc[i].y-focalp.y) * position;
	  glColor4us(color->red, color->green, color->blue, alpha);
	  glVertex2d(x, y);
	  x = focalp.x + (tarc[i].x-focalp.x) * position3;
	  y = focalp.y + (tarc[i].y-focalp.y) * position3;
	  glColor4us(color2->red, color2->green, color2->blue, alpha);
	  glVertex2d(x, y);
	}
	position = position3;
	color = color2;
	alpha = alpha2;
	glEnd();
      }
      glBegin(GL_QUAD_STRIP);
      color2 = gradient->actual_colors[j].rgb;
      alpha2 = ZnComposeAlpha(gradient->actual_colors[j].alpha, wi->alpha);
      for (i = 0; i < num_p; i++) {
	x = focalp.x + (tarc[i].x-focalp.x) * position;
	y = focalp.y + (tarc[i].y-focalp.y) * position;
	glColor4us(color->red, color->green, color->blue, alpha);
	glVertex2d(x, y);
	x = focalp.x + (tarc[i].x-focalp.x) * position2;
	y = focalp.y + (tarc[i].y-focalp.y) * position2;
	glColor4us(color2->red, color2->green, color2->blue, alpha2);
	glVertex2d(x, y);
      }
      glEnd();
      position = position2;
      color = color2;
      alpha = alpha2;
      control = gradient->actual_colors[j].control;
    }
  }
  else if (type == ZN_PATH_GRADIENT) {
    ZnPoint	 p, pp, p2, pp2, p3, pp3;
    unsigned int num_p, k, ii;
    ZnPoint	 *points;
    ZnReal	 position;
    
    for (k = 0; k < poly->num_contours; k++) {
      /*if (poly->contours[k].cw) {
	continue;
	}*/
      points = poly->contours[k].points;
      num_p = poly->contours[k].num_points;
      
      for (i = 0; i < num_p; i++) {
	if (i == num_p-1) {
	  ii = 0;
	}
	else {
	  ii = i+1;
	}
	
	glBegin(GL_QUAD_STRIP);
	p.x = p.y = pp.x = pp.y = 0;
	control = gradient->actual_colors[0].control;
	position = gradient->actual_colors[0].position;
	alpha = ZnComposeAlpha(gradient->actual_colors[0].alpha, wi->alpha);
	color = gradient->actual_colors[0].rgb;
	glColor4us(color->red, color->green, color->blue, alpha);
	glVertex2d(quad[0].x+p.x, quad[0].y+p.y);
	glVertex2d(quad[0].x+pp.x, quad[0].y+pp.y);
	for (j = 0; j < gradient->num_actual_colors-1; j++) {
	  position = gradient->actual_colors[j+1].position;
	  p2.x = (points[i].x-quad[0].x)*position/100.0;
	  p2.y = (points[i].y-quad[0].y)*position/100.0;
	  pp2.x = (points[ii].x-quad[0].x)*position/100.0;
	  pp2.y = (points[ii].y-quad[0].y)*position/100.0;
	  if (control != 50) {
	    color = gradient->actual_colors[j].mid_rgb;
	    alpha = ZnComposeAlpha(gradient->actual_colors[j].mid_alpha, wi->alpha);
	    p3.x = p.x+(p2.x-p.x)*control/100.0;
	    p3.y = p.y+(p2.y-p.y)*control/100.0;
	    pp3.x = pp.x+(pp2.x-pp.x)*control/100.0;
	    pp3.y = pp.y+(pp2.y-pp.y)*control/100.0;
	    glColor4us(color->red, color->green, color->blue, alpha);
	    glVertex2d(quad[0].x+p3.x, quad[0].y+p3.y);
	    glVertex2d(quad[0].x+pp3.x, quad[0].y+pp3.y);
	  }
	  control = gradient->actual_colors[j+1].control;
	  alpha = ZnComposeAlpha(gradient->actual_colors[j+1].alpha, wi->alpha);
	  color = gradient->actual_colors[j+1].rgb;
	  p = p2;
	  pp = pp2;
	  glColor4us(color->red, color->green, color->blue, alpha);
	  glVertex2d(quad[0].x+p.x, quad[0].y+p.y);
	  glVertex2d(quad[0].x+pp.x, quad[0].y+pp.y);
	}
	glEnd();
      }
    }
  }
  else if (type == ZN_CONICAL_GRADIENT) {
    ZnReal	 position;
    unsigned int num_p;
    ZnPoint	 *genarc, *tarc, p, focalp;
    XColor	 col;

    genarc = ZnGetCirclePoints(3, ZN_CIRCLE_FINEST, 0.0, 2*M_PI, &num_p, NULL);
    ZnListAssertSize(wi->work_pts, num_p);
    tarc = ZnListArray(wi->work_pts);
    ZnTransformPoints((ZnTransfo *) quad, genarc, tarc, num_p);
    p.x = p.y = 0;
    ZnTransformPoint((ZnTransfo *) quad, &p, &focalp);
    
    glBegin(GL_TRIANGLE_STRIP);
    for (i = 0; i < num_p; i++) {
      position = i*100.0/(num_p-1);
      ZnInterpGradientColor(gradient, position, &col, &alpha);
      alpha = ZnComposeAlpha(alpha, wi->alpha);

      /*printf("position: %g --> color: %d %d %d, alpha: %d\n",
	position, col.red, col.green, col.blue, alpha);*/

      glColor4us(col.red, col.green, col.blue, alpha);
      glVertex2d(tarc[i].x, tarc[i].y);
      glVertex2d(focalp.x, focalp.y);
    }
    glEnd();
  }
  
  if (cb) {
    /*
     * Restore the previous GL state.
     */
    ZnGlEndClip(num_clips);
  }
}


void
ZnRenderHollowDot(ZnWInfo	*wi,
		  ZnPoint	*p,
		  ZnReal	size)
{
  int	num_clips = ZnListSize(wi->clip_stack);
  
  ZnGlStartClip(num_clips, False);
  
  glPointSize((GLfloat) (size-2));
  glBegin(GL_POINTS);
  glVertex2d(p->x, p->y);
  glEnd();

  ZnGlRenderClipped();

  glPointSize((GLfloat) size);
  glBegin(GL_POINTS);
  glVertex2d(p->x, p->y);
  glEnd();

  ZnGlRestoreStencil(num_clips, False);

  glBegin(GL_POINTS);
  glVertex2d(p->x, p->y);
  glEnd();

  ZnGlEndClip(num_clips);
}
#endif


#ifdef GL
/* Copyright (c) Mark J. Kilgard, 1997. */

/* This program is freely distributable without licensing fees  and is
   provided without guarantee or warrantee expressed or  implied. This
   program is -not- in the public domain. */

void
ZnRenderGlyph(ZnTexFontInfo	*tfi,
	      int		c)
{
  ZnTexGVI *tgvi;

  tgvi = ZnTexFontGVI(tfi, c);	 
  glBegin(GL_QUADS);
  glTexCoord2fv(tgvi->t0);
  glVertex2sv(tgvi->v0);
  glTexCoord2fv(tgvi->t1);
  glVertex2sv(tgvi->v1);
  glTexCoord2fv(tgvi->t2);
  glVertex2sv(tgvi->v2);
  glTexCoord2fv(tgvi->t3);
  glVertex2sv(tgvi->v3);
  glEnd();
  glTranslatef(tgvi->advance, 0.0, 0.0);
}

void
ZnRenderString(ZnTexFontInfo	*tfi,
	       unsigned char	*string,
	       unsigned int	len)
{
  unsigned int i;

  for (i = 0; i < len; i++) {
    ZnRenderGlyph(tfi, string[i]);
  }
}

enum {
  MONO, TOP_BOTTOM, LEFT_RIGHT, FOUR
};

void
ZnRenderFancyString(ZnTexFontInfo	*tfi,
		    unsigned char	*string,
		    unsigned int	len)
{
  ZnTexGVI	*tgvi;
  GLubyte	c[4][3];
  int		mode = MONO;
  unsigned int	i;

  for (i = 0; i < len; i++) {
    if (string[i] == 27) {
      switch (string[i + 1]) {
      case 'M':
        mode = MONO;
        glColor3ubv((GLubyte *) & string[i + 2]);
        i += 4;
        break;
      case 'T':
        mode = TOP_BOTTOM;
        memcpy(c, &string[i + 2], 6);
        i += 7;
        break;
      case 'L':
        mode = LEFT_RIGHT;
        memcpy(c, &string[i + 2], 6);
        i += 7;
        break;
      case 'F':
        mode = FOUR;
        memcpy(c, &string[i + 2], 12);
        i += 13;
        break;
      }
    } else {
      switch (mode) {
      case MONO:
        ZnRenderGlyph(tfi, string[i]);
        break;
      case TOP_BOTTOM:
        tgvi = ZnTexFontGVI(tfi, string[i]);
        glBegin(GL_QUADS);
        glColor3ubv(c[0]);
        glTexCoord2fv(tgvi->t0);
        glVertex2sv(tgvi->v0);
        glTexCoord2fv(tgvi->t1);
        glVertex2sv(tgvi->v1);
        glColor3ubv(c[1]);
        glTexCoord2fv(tgvi->t2);
        glVertex2sv(tgvi->v2);
        glTexCoord2fv(tgvi->t3);
        glVertex2sv(tgvi->v3);
        glEnd();
        glTranslatef(tgvi->advance, 0.0, 0.0);
        break;
      case LEFT_RIGHT:
        tgvi = ZnTexFontGVI(tfi, string[i]);
        glBegin(GL_QUADS);
        glColor3ubv(c[0]);
        glTexCoord2fv(tgvi->t0);
        glVertex2sv(tgvi->v0);
        glColor3ubv(c[1]);
        glTexCoord2fv(tgvi->t1);
        glVertex2sv(tgvi->v1);
        glColor3ubv(c[1]);
        glTexCoord2fv(tgvi->t2);
        glVertex2sv(tgvi->v2);
        glColor3ubv(c[0]);
        glTexCoord2fv(tgvi->t3);
        glVertex2sv(tgvi->v3);
        glEnd();
        glTranslatef(tgvi->advance, 0.0, 0.0);
        break;
      case FOUR:
        tgvi = ZnTexFontGVI(tfi, string[i]);
        glBegin(GL_QUADS);
        glColor3ubv(c[0]);
        glTexCoord2fv(tgvi->t0);
        glVertex2sv(tgvi->v0);
        glColor3ubv(c[1]);
        glTexCoord2fv(tgvi->t1);
        glVertex2sv(tgvi->v1);
        glColor3ubv(c[2]);
        glTexCoord2fv(tgvi->t2);
        glVertex2sv(tgvi->v2);
        glColor3ubv(c[3]);
        glTexCoord2fv(tgvi->t3);
        glVertex2sv(tgvi->v3);
        glEnd();
        glTranslatef(tgvi->advance, 0.0, 0.0);
        break;
      }
    }
  }
}

#endif

/*
 **********************************************************************************
 *
 * RenderTriangle --
 *	This routine maps an image onto a triangle.
 *	Image coordinates are chosen for  each vertex of the triangle.
 *	A simple affine tex mapping is used as in Zinc there is no way
 *	to specify perspective deformation. No filtering is attempted
 *	on the output pixels. 
 *
 *	In the comments below u and v are image coordinates and x and
 *	y are triangle coordinates.
 *	RenderAffineScanline is an helper function that draws a whole
 *	scan line to the image with linear interpolation.
 *
 **********************************************************************************
 */
static void
RenderAffineScanline(XImage	*image,
		     XImage	*mapped_image,
		     ZnReal	x1,
		     ZnReal	x2,
		     ZnReal	u1,
		     ZnReal	u2,
		     ZnReal	v1,
		     ZnReal	v2,
		     int	y)
{
  ZnReal	du, dv, width;
  int		intx1, intx2, intu, intv;
  int		i;
  
  /* Revert span ends if needed */
  if (x2 < x1) {
    ZnReal	tmp;
    tmp = x1; x1 = x2; x2 = tmp;
    tmp = u1; u1 = u2; u2 = tmp;
    tmp = v1; v1 = v2; v2 = tmp;
  }
  
  /* Compute the interpolation factors */
  width = x2 - x1;
  if (width) {
    du = (u2 - u1) / width;
    dv = (v2 - v1) / width;
  }
  else {
    du = dv = 0;
  }
  intx1 = (int) floor(x1);
  intx2 = (int) floor(x2);
  
  /* Draw the line */
  for (i = intx1; i < intx2; i++) {
    intu = (int) floor(u1);
    intv = (int) floor(v1);
    XPutPixel(mapped_image, i, y, XGetPixel(image, intu, intv));
    u1 += du;
    v1 += dv;
  }
}

static void
RenderTriangle(XImage	*image,
	       XImage	*mapped_image,
	       ZnPoint	*tri,
	       ZnPoint	*im_coords)
{
  ZnReal	dx_A, dx_B;	/* Interpolation factor in x / y */
  ZnReal	du_A, du_B;	/* in u / y */
  ZnReal	dv_A, dv_B;	/* in v / y */
  ZnReal	x1, x2;		/* Span in x */
  ZnReal	u1, u2;		/* Span in u */
  ZnReal	v1, v2;		/* Span in v */
  int		height_A;	/* Scan line # from top top vertex A */
  int		height_B;	/* Scan line # from top top vertex B */
  int		y;		/* Current scan line */
  int		top, a, b;	/* Top triangle vertex and other two */
  int		i;

  /* Find top vertex and deduce the others. */
  top = 0;
  for (i = 1; i < 3; i++) {
    if (tri[i].y <= tri[top].y)
      top = i;
  }
  a = (top+1)%3;
  b = top-1;
  if (b < 0)
    b = 2;
  
  /* Initialize conversion parameters. */
  y = ZnNearestInt(tri[top].y);
  height_A = ZnNearestInt(tri[a].y - tri[top].y);
  height_B = ZnNearestInt(tri[b].y - tri[top].y);
  x1 = x2 = tri[top].x;
  u1 = u2 = im_coords[top].x;
  v1 = v2 = im_coords[top].y;
  if (height_A) {
    dx_A = (tri[a].x - tri[top].x) / height_A;
    du_A = (im_coords[a].x - im_coords[top].x) / height_A;
    dv_A = (im_coords[a].y - im_coords[top].y) / height_A;
  }
  else {
    dx_A = du_A = dv_A = 0;
  }
  if (height_B) {
    dx_B = (tri[b].x - tri[top].x) / height_B;
    du_B = (im_coords[b].x - im_coords[top].x) / height_B;
    dv_B = (im_coords[b].y - im_coords[top].y) / height_B;
  }
  else {
    dx_B = du_B = dv_B = 0;
  }
  
  /* Convert from top to bottom */
  for (i = 2; i > 0; ) {
    while (height_A && height_B) {

      /* Draw a scanline */
      RenderAffineScanline(image, mapped_image, x1, x2, u1, u2, v1, v2, y);

      /* Step the parameters*/
      y++;
      height_A--;
      height_B--;
      x1 += dx_A;
      x2 += dx_B;
      u1 += du_A;
      u2 += du_B;
      v1 += dv_A;
      v2 += dv_B;
    }
    
    /* If either height_A or height_B steps to zero, we have
     * encountered a vertex (A or B) and we are starting conversion
     * along a new edge. Update the parameters before proceeding. */
    if (!height_A) {
      int	na = (a+1)%3;
      
      height_A = ZnNearestInt(tri[na].y - tri[a].y);
      if (height_A) {
	dx_A = (tri[na].x - tri[a].x) / height_A;
	du_A = (im_coords[na].x - im_coords[a].x) / height_A;
	dv_A = (im_coords[na].y - im_coords[a].y) / height_A;
      }
      else {
	dx_A = du_A = dv_A = 0;
      }
      x1 = tri[a].x;
      u1 = im_coords[a].x;
      v1 = im_coords[a].y;
      a = na;
      /* One less vertex to do */
      i--;
    }
    
    if (!height_B) {
      int	nb = b - 1;
      
      if (nb < 0)
	nb = 2;
      height_B = ZnNearestInt(tri[nb].y - tri[b].y);
      if (height_B) {
	dx_B = (tri[nb].x - tri[b].x) / height_B;
	du_B = (im_coords[nb].x - im_coords[b].x) / height_B;
	dv_B = (im_coords[nb].y - im_coords[b].y) / height_B;
      }
      else {
	dx_B = du_B = dv_B = 0;
      }
      x2 = tri[b].x;
      u2 = im_coords[b].x;
      v2 = im_coords[b].y;
      b = nb;
      /* One less vertex to do */
      i--;
    }
  }
}


/*
 **********************************************************************************
 *
 * MapImage --
 *	This procedure maps an image on a parallelogram given in poly.
 *	The given parallelogram should fit in the destination image.
 *	The parallelogram vertices must be ordered as for a triangle
 *	 strip: 
 *
 *    v0 ------------ v2
 *       |          |
 *       |          |
 *    v1 ------------ v3
 *
 *	The mapping is done by a simple affine mapping of the image on the
 *	two triangles obtained by cutting the parallelogram along the diogonal
 *	from the second vertex to the third vertex.
 *
 **********************************************************************************
 */
void
ZnMapImage(XImage	*image,
	   XImage	*mapped_image,
	   ZnPoint	*poly)
{
  ZnPoint	triangle[3];
  ZnPoint	im_coords[3];
  
  triangle[0] = poly[0];
  triangle[1] = poly[1];
  triangle[2] = poly[2];
  im_coords[0].x = 0.0;
  im_coords[0].y = 0.0;
  im_coords[1].x = 0.0;
  im_coords[1].y = image->height-1;
  im_coords[2].x = image->width-1;
  im_coords[2].y = 0.0;
  RenderTriangle(image, mapped_image, triangle, im_coords);

  triangle[0] = poly[1];
  triangle[1] = poly[2];
  triangle[2] = poly[3];
  im_coords[0].x = 0.0;
  im_coords[0].y = image->height-1;
  im_coords[1].x = image->width-1;
  im_coords[1].y = 0.0;
  im_coords[2].x = image->width-1;
  im_coords[2].y = image->height-1;
  RenderTriangle(image, mapped_image, triangle, im_coords);
}
