/** <title>NSBezierPath.m</title>

   <abstract>The NSBezierPath class</abstract>

   Copyright (C) 1999 Free Software Foundation, Inc.

   Author:  Enrico Sersale <enrico@imago.ro>
   Date: Dec 1999
   Modified:  Fred Kiefer <FredKiefer@gmx.de>
   Date: January 2001
   
   This file is part of the GNUstep GUI Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111 - 1307, USA.
*/

#include "AppKit/NSBezierPath.h"

#include "AppKit/NSAffineTransform.h"
#include "AppKit/NSFont.h"
#include "AppKit/NSImage.h"
#include "AppKit/PSOperators.h"
#include "GNUstepGUI/GSFontInfo.h"

#include <math.h>

#ifndef PI
#define PI 3.1415926535897932384626433
#endif

// This magic number is 4 *(sqrt(2) -1)/3
#define KAPPA 0.5522847498
#define INVALIDATE_CACHE()   [self _invalidateCache]

static void flatten(NSPoint coeff[], float flatness, NSBezierPath *path);
static Class NSBezierPath_concrete_class = nil;

static NSWindingRule default_winding_rule = NSNonZeroWindingRule;
static float default_line_width = 1.0;
static float default_flatness = 1.0;
static NSLineJoinStyle default_line_join_style = NSMiterLineJoinStyle;
static NSLineCapStyle default_line_cap_style = NSButtLineCapStyle;
static float default_miter_limit = 10.0;

@interface NSBezierPath (PrivateMethods)

- (void)_invalidateCache;
- (void)_recalculateBounds;

@end

@class GSBezierPath;

@implementation NSBezierPath

+ (void)initialize
{
  if(self == [NSBezierPath class])
    NSBezierPath_concrete_class = [GSBezierPath class];
}

+ (void)_setConcreteClass:(Class)c
{
  NSBezierPath_concrete_class = c;
}

+ (Class)_concreteClass
{
  return NSBezierPath_concrete_class;
}

//
// Creating common paths
//
+ (id) allocWithZone: (NSZone*)z
{
  if (self != NSBezierPath_concrete_class)
    {
      return [NSBezierPath_concrete_class allocWithZone: z];
    }
  else
    {
      return NSAllocateObject (self, 0, z);
    }    
}

+ (NSBezierPath *)bezierPath
{
  return AUTORELEASE ([[NSBezierPath_concrete_class alloc] init]);
}

+ (NSBezierPath *)bezierPathWithRect: (NSRect)aRect
{
  NSBezierPath *path;
	
  path = [self bezierPath];
  [path appendBezierPathWithRect: aRect];
	
  return path;
}

+ (NSBezierPath *)bezierPathWithOvalInRect: (NSRect)aRect
{
  NSBezierPath *path;
  
  path = [self bezierPath];
  [path appendBezierPathWithOvalInRect: aRect];

  return path;
}

//
// Immediate mode drawing of common paths
//
+ (void)fillRect: (NSRect)aRect
{
  PSrectfill(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect),  NSHeight(aRect));
}

+ (void)strokeRect: (NSRect)aRect
{
  PSrectstroke(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect),  NSHeight(aRect));
}

+ (void)clipRect: (NSRect)aRect
{
  PSrectclip(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect),  NSHeight(aRect));
}

+ (void)strokeLineFromPoint: (NSPoint)point1  toPoint: (NSPoint)point2
{
  NSBezierPath *path = [[self alloc] init];
  
  [path moveToPoint: point1];
  [path lineToPoint: point2];
  [path stroke];
  RELEASE(path);
}

+ (void)drawPackedGlyphs: (const char *)packedGlyphs  atPoint: (NSPoint)aPoint
{
  NSBezierPath *path = [[self alloc] init];
  
  [path moveToPoint: aPoint];
  [path appendBezierPathWithPackedGlyphs: packedGlyphs];
  [path stroke];  
  RELEASE(path);
}

//
// Default path rendering parameters
//
+ (void)setDefaultMiterLimit:(float)limit
{
  default_miter_limit = limit;
  // Do we need this?
  PSsetmiterlimit(limit);
}

+ (float)defaultMiterLimit
{
  return default_miter_limit;
}

+ (void)setDefaultFlatness:(float)flatness
{
  default_flatness = flatness;
  PSsetflat(flatness);
}

+ (float)defaultFlatness
{
  return default_flatness;
}

+ (void)setDefaultWindingRule:(NSWindingRule)windingRule
{
  default_winding_rule = windingRule;
}

+ (NSWindingRule)defaultWindingRule
{
  return default_winding_rule;
}

+ (void)setDefaultLineCapStyle:(NSLineCapStyle)lineCapStyle
{
  default_line_cap_style = lineCapStyle;
  PSsetlinecap(lineCapStyle);
}

+ (NSLineCapStyle)defaultLineCapStyle
{
  return default_line_cap_style;
}

+ (void)setDefaultLineJoinStyle:(NSLineJoinStyle)lineJoinStyle
{
  default_line_join_style = lineJoinStyle;
  PSsetlinejoin(lineJoinStyle);
}

+ (NSLineJoinStyle)defaultLineJoinStyle
{
  return default_line_join_style;
}

+ (void)setDefaultLineWidth:(float)lineWidth
{
  default_line_width = lineWidth;
  PSsetlinewidth(lineWidth);
}

+ (float)defaultLineWidth
{
  return default_line_width;
}

- (id) init
{
  [super init];

  // Those values come from the default.
  [self setLineWidth: default_line_width];
  [self setFlatness: default_flatness];
  [self setLineCapStyle: default_line_cap_style];
  [self setLineJoinStyle: default_line_join_style];
  [self setMiterLimit: default_miter_limit];
  [self setWindingRule: default_winding_rule];
  // Set by allocation
  //_bounds = NSZeroRect;
  //_controlPointBounds = NSZeroRect;
  //_cachesBezierPath = NO;
  //_cacheImage = nil;
  //_dash_count = 0;
  //_dash_phase = 0;
  //_dash_pattern = NULL; 

  return self;
}

- (void) dealloc
{
  if(_cacheImage != nil)
    RELEASE(_cacheImage);

  if (_dash_pattern != NULL)
    NSZoneFree([self zone], _dash_pattern);

  [super dealloc];
}

//
// Path construction
//
- (void)moveToPoint:(NSPoint)aPoint
{
  [self subclassResponsibility:_cmd];
}

- (void)lineToPoint:(NSPoint)aPoint
{
  [self subclassResponsibility:_cmd];
}

- (void)curveToPoint:(NSPoint)aPoint 
       controlPoint1:(NSPoint)controlPoint1
       controlPoint2:(NSPoint)controlPoint2
{
  [self subclassResponsibility:_cmd];
}

- (void)closePath
{
  [self subclassResponsibility:_cmd];
}

- (void)removeAllPoints
{
  [self subclassResponsibility:_cmd];
}

//
// Relative path construction
//
- (void)relativeMoveToPoint:(NSPoint)aPoint
{
  NSPoint p = [self currentPoint];

  p.x = p.x + aPoint.x;
  p.y = p.y + aPoint.y;
  [self moveToPoint: p];
}

- (void)relativeLineToPoint:(NSPoint)aPoint
{
  NSPoint p = [self currentPoint];

  p.x = p.x + aPoint.x;
  p.y = p.y + aPoint.y;
  [self lineToPoint: p];
}

- (void)relativeCurveToPoint:(NSPoint)aPoint
	       controlPoint1:(NSPoint)controlPoint1
	       controlPoint2:(NSPoint)controlPoint2
{
  NSPoint p = [self currentPoint];

  aPoint.x = p.x + aPoint.x;
  aPoint.y = p.y + aPoint.y;
  controlPoint1.x = p.x + controlPoint1.x;
  controlPoint1.y = p.y + controlPoint1.y;
  controlPoint2.x = p.x + controlPoint2.x;
  controlPoint2.y = p.y + controlPoint2.y;
  [self curveToPoint: aPoint
	controlPoint1: controlPoint1
	controlPoint2: controlPoint2];
}

//
// Path rendering parameters
//
- (float)lineWidth
{
  return _lineWidth;
}

- (void)setLineWidth:(float)lineWidth
{
  _lineWidth = lineWidth;
}

- (NSLineCapStyle)lineCapStyle
{
  return _lineCapStyle;
}

- (void)setLineCapStyle:(NSLineCapStyle)lineCapStyle
{
  _lineCapStyle = lineCapStyle;
}

- (NSLineJoinStyle)lineJoinStyle
{
  return _lineJoinStyle;
}

- (void)setLineJoinStyle:(NSLineJoinStyle)lineJoinStyle
{
  _lineJoinStyle = lineJoinStyle;
}

- (NSWindingRule)windingRule
{
  return _windingRule;
}

- (void)setWindingRule:(NSWindingRule)windingRule
{
  _windingRule = windingRule;
}

- (void)setFlatness:(float)flatness
{
  _flatness = flatness;
}

- (float)flatness
{
  return _flatness;
}

- (void)setMiterLimit:(float)limit
{
  _miterLimit = limit;
}

- (float)miterLimit
{
  return _miterLimit;
}

- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase
{
  // FIXME: How big is the pattern array? 
  // We assume that this value is in count!
  if (count != NULL)
    {
      if (*count < _dash_count)
        {
	  *count = _dash_count;
	  return;
	}
      *count = _dash_count;
    }

  if (phase != NULL)
    *phase = _dash_phase;

  memcpy(pattern, _dash_pattern, _dash_count * sizeof(float));
}

- (void)setLineDash:(const float *)pattern count:(int)count phase:(float)phase
{
  NSZone *myZone = [self zone];
  
  if ((pattern == NULL) || (count == 0))
    {
      if (_dash_pattern != NULL)
        {
	  NSZoneFree(myZone, _dash_pattern);
	  _dash_pattern = NULL;
	}
      _dash_count = 0;
      _dash_phase = 0.0;
      return;
    }

  if (_dash_pattern == NULL)
    _dash_pattern = NSZoneMalloc(myZone, count * sizeof(float));
  else
    NSZoneRealloc(myZone, _dash_pattern, count * sizeof(float));

  _dash_count = count;
  _dash_phase = phase;
  memcpy(_dash_pattern, pattern, _dash_count * sizeof(float));
}

//
// Path operations
//
- (void)stroke
{
  NSGraphicsContext *ctxt = GSCurrentContext();
  
  if(_cachesBezierPath) 
    {
      NSRect bounds = [self bounds];
      NSPoint origin = bounds.origin;

      // FIXME: I don't see how this should work with color changes
      if(_cacheImage == nil) 
        {
	  _cacheImage = [[NSImage alloc] initWithSize: bounds.size];		
	  [_cacheImage lockFocus];
	  DPStranslate(ctxt, -origin.x, -origin.y);
	  [ctxt GSSendBezierPath: self];
	  DPSstroke(ctxt);
	  [_cacheImage unlockFocus];						
	}
      [_cacheImage compositeToPoint: origin operation: NSCompositeCopy];
    } 
  else 
    {
      [ctxt GSSendBezierPath: self];
      DPSstroke(ctxt);
    }
}

- (void)fill
{
  NSGraphicsContext *ctxt = GSCurrentContext();

  if(_cachesBezierPath) 
    {
      NSRect bounds = [self bounds];
      NSPoint origin = bounds.origin;

      // FIXME: I don't see how this should work with color changes
      if(_cacheImage == nil) 
        {
	  _cacheImage = [[NSImage alloc] initWithSize: bounds.size];		
	  [_cacheImage lockFocus];
	  DPStranslate(ctxt, -origin.x, -origin.y);
	  [ctxt GSSendBezierPath: self];
	  if([self windingRule] == NSNonZeroWindingRule)
	    DPSfill(ctxt);
	  else
	    DPSeofill(ctxt);
	  [_cacheImage unlockFocus];
	}
      [_cacheImage compositeToPoint: origin operation: NSCompositeCopy];
    } 
  else 
    {
      [ctxt GSSendBezierPath: self];
      if([self windingRule] == NSNonZeroWindingRule)
	DPSfill(ctxt);
      else
	DPSeofill(ctxt);
    }
}

- (void)addClip
{
  NSGraphicsContext *ctxt = GSCurrentContext();
  
  [ctxt GSSendBezierPath: self];
  if([self windingRule] == NSNonZeroWindingRule)
    DPSclip(ctxt);
  else
    DPSeoclip(ctxt);
}

- (void)setClip
{
  NSGraphicsContext *ctxt = GSCurrentContext();
  
  DPSinitclip(ctxt);
  [ctxt GSSendBezierPath: self];
  if([self windingRule] == NSNonZeroWindingRule)
    DPSclip(ctxt);
  else
    DPSeoclip(ctxt);
}

//
// Path modifications.
//
- (NSBezierPath *)bezierPathByFlatteningPath
{
  NSBezierPath *path = [isa bezierPath];
  NSBezierPathElement type;
  NSPoint pts[3];
  NSPoint coeff[4];
  NSPoint p, last_p;
  int i, count;
  BOOL first = YES;

  count = [self elementCount];
  for(i = 0; i < count; i++) 
    {
      type = [self elementAtIndex: i associatedPoints: pts];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	      [path moveToPoint: pts[0]];
	      last_p = p = pts[0];
	      first = NO;
	      break;
	  case NSLineToBezierPathElement:
	      [path lineToPoint: pts[0]];
	      p = pts[0];
	      if (first)
	        {
		  last_p = pts[0];
		  first = NO;
		}
	      break;
	  case NSCurveToBezierPathElement:
	      coeff[0] = p;
	      coeff[1] = pts[0];
	      coeff[2] = pts[1];
	      coeff[3] = pts[2];
      	      flatten(coeff, [self flatness], path);
	      p = pts[2];
	      if (first)
	        {
		  last_p = pts[2];
		  first = NO;
		}
	      break;
	  case NSClosePathBezierPathElement:
	      [path closePath];
	      p = last_p;
	      break;
	  default:
	      break;
	}
    }

  return path;
}

- (NSBezierPath *) bezierPathByReversingPath
{
  NSBezierPath *path = [isa bezierPath];
  NSBezierPathElement type, last_type;
  NSPoint pts[3];
  NSPoint p, cp1, cp2;
  int i, j, count;
  BOOL closed = NO;

  last_type = NSMoveToBezierPathElement;
  count = [self elementCount];
  for(i = count - 1; i >= 0; i--) 
    {
      type = [self elementAtIndex: i associatedPoints: pts];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	      p = pts[0];
	      break;
	  case NSLineToBezierPathElement:
	      p = pts[0];
	      break;
	  case NSCurveToBezierPathElement:
	      cp1 = pts[0];
	      cp2 = pts[1];
	      p = pts[2];      
	      break;
	  case NSClosePathBezierPathElement:
	      // find the first point of segment
	      for (j = i - 1; j >= 0; j--) 
	        {
		  type = [self elementAtIndex: i associatedPoints: pts];
		  if (type == NSMoveToBezierPathElement)
		    {
		      p = pts[0];
		      break;
		    }   
		}
	      // FIXME: What to do if we don't find a move element?
	      break;
	  default:
	      break;
	}

      switch(last_type) 
        {
	  case NSMoveToBezierPathElement:
	      if (closed)
	        {
		  [path closePath];
		  closed = NO;
		}
	      [path moveToPoint: p];
	      break;
	  case NSLineToBezierPathElement:
	      [path lineToPoint: p];
	      break;
	  case NSCurveToBezierPathElement:
	      [path curveToPoint: p 
		    controlPoint1: cp2 
		    controlPoint2: cp1];	      
	      break;
	  case NSClosePathBezierPathElement:
	      closed = YES;
	      break;
	  default:
	      break;
	}
      last_type = type;
    }

  if (closed)
    [path closePath];
  return path;
}

//
// Applying transformations.
//
- (void) transformUsingAffineTransform: (NSAffineTransform *)transform
{
  NSBezierPathElement type;
  NSPoint pts[3];
  int i, count;

  count = [self elementCount];
  for(i = 0; i < count; i++) 
    {
      type = [self elementAtIndex: i associatedPoints: pts];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	  case NSLineToBezierPathElement:
	      pts[0] = [transform transformPoint: pts[0]];
	      [self setAssociatedPoints: pts atIndex: i];
	      break;
	  case NSCurveToBezierPathElement:
	      pts[0] = [transform transformPoint: pts[0]];
	      pts[1] = [transform transformPoint: pts[1]];
	      pts[2] = [transform transformPoint: pts[2]];
	      [self setAssociatedPoints: pts atIndex: i];
	      break;
	  case NSClosePathBezierPathElement:
	      break;
	  default:
	      break;
	}
    }
  INVALIDATE_CACHE();
}

//
// Path info
//
- (BOOL) isEmpty
{
  return ([self elementCount] == 0);
}

- (NSPoint) currentPoint
{
  NSBezierPathElement type;
  NSPoint points[3];
  int i, count;

  count = [self elementCount];
  if (!count) 
    [NSException raise: NSGenericException
		 format: @"No current Point in NSBezierPath"];

  type = [self elementAtIndex: count - 1 associatedPoints: points];
  switch(type) 
    {
      case NSMoveToBezierPathElement:
      case NSLineToBezierPathElement:
	  return points[0];
	  break;
      case NSCurveToBezierPathElement:
	  return points[2];
	  break;
      case NSClosePathBezierPathElement:
	  // We have to find the last move element and take its point
	  for (i = count - 2; i >= 0; i--)
	    {
	      type = [self elementAtIndex: i associatedPoints: points];
	      if (type == NSMoveToBezierPathElement)
		return points[0];
	    }
	  break;
      default:
	  break;
    }

  return NSZeroPoint;
}

- (NSRect) controlPointBounds
{
  if (_shouldRecalculateBounds)
     [self _recalculateBounds];
  return _controlPointBounds;
}

- (NSRect) bounds
{
  if (_shouldRecalculateBounds)
     [self _recalculateBounds];
  return _bounds;
}

//
// Elements
//
- (int) elementCount
{
  [self subclassResponsibility:_cmd];
  return 0;
}

- (NSBezierPathElement) elementAtIndex: (int)index
		      associatedPoints: (NSPoint *)points
{
  [self subclassResponsibility:_cmd];	
  return 0;
}

- (NSBezierPathElement) elementAtIndex: (int)index
{
  return [self elementAtIndex: index associatedPoints: NULL];	
}

- (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index
{
  [self subclassResponsibility:_cmd];	
}

//
// Appending common paths
//
- (void) appendBezierPath: (NSBezierPath *)aPath
{
  NSBezierPathElement type;
  NSPoint points[3];
  int i, count;

  count = [aPath elementCount];
  for (i = 0; i < count; i++)
    {
      type = [aPath elementAtIndex: i associatedPoints: points];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	      [self moveToPoint: points[0]];
	      break;
	  case NSLineToBezierPathElement:
	      [self lineToPoint: points[0]];
	      break;
	  case NSCurveToBezierPathElement:
	      [self curveToPoint: points[2] 
		    controlPoint1: points[0]
		    controlPoint2: points[1]];
	      break;
	  case NSClosePathBezierPathElement:
	      [self closePath];
	      break;
	  default:
	      break;
	}
    }
}

- (void)appendBezierPathWithRect:(NSRect)aRect
{
  NSPoint p;
	
  [self moveToPoint: aRect.origin];
  p.x = aRect.origin.x + aRect.size.width;
  p.y = aRect.origin.y;
  [self lineToPoint: p];
  p.x = aRect.origin.x + aRect.size.width;
  p.y = aRect.origin.y + aRect.size.height;
  [self lineToPoint: p];
  p.x = aRect.origin.x;
  p.y = aRect.origin.y + aRect.size.height;
  [self lineToPoint: p];
  [self closePath];
}

- (void)appendBezierPathWithPoints:(NSPoint *)points count:(int)count
{
  int i;

  if (!count)
    return;

  if ([self isEmpty])
    {
      [self moveToPoint: points[0]];
    }
  else
    {
      [self lineToPoint: points[0]];
    }
  
  for (i = 1; i < count; i++)
    {
      [self lineToPoint: points[i]];
    }
}

- (void) appendBezierPathWithOvalInRect: (NSRect)aRect
{
  NSPoint p, p1, p2;
  double originx = aRect.origin.x;
  double originy = aRect.origin.y;
  double width = aRect.size.width;
  double height = aRect.size.height;
  double hdiff = width / 2 * KAPPA;
  double vdiff = height / 2 * KAPPA;
  
  p = NSMakePoint(originx + width / 2, originy + height);
  [self moveToPoint: p];
  
  p = NSMakePoint(originx, originy + height / 2);
  p1 = NSMakePoint(originx + width / 2 - hdiff, originy + height);
  p2 = NSMakePoint(originx, originy + height / 2 + vdiff);
  [self curveToPoint: p controlPoint1: p1 controlPoint2: p2];
  
  p = NSMakePoint(originx + width / 2, originy);
  p1 = NSMakePoint(originx, originy + height / 2 - vdiff);
  p2 = NSMakePoint(originx + width / 2 - hdiff, originy);
  [self curveToPoint: p controlPoint1: p1 controlPoint2: p2];	
  
  p = NSMakePoint(originx + width, originy + height / 2);
  p1 = NSMakePoint(originx + width / 2 + hdiff, originy);
  p2 = NSMakePoint(originx + width, originy + height / 2 - vdiff);
  [self curveToPoint: p controlPoint1: p1 controlPoint2: p2];	
  
  p = NSMakePoint(originx + width / 2, originy + height);
  p1 = NSMakePoint(originx + width, originy + height / 2 + vdiff);
  p2 = NSMakePoint(originx + width / 2 + hdiff, originy + height);
  [self curveToPoint: p controlPoint1: p1 controlPoint2: p2];	
}

/* startAngle and endAngle are in degrees, counterclockwise, from the
   x axis */
- (void) appendBezierPathWithArcWithCenter: (NSPoint)center  
				    radius: (float)radius
				startAngle: (float)startAngle
				  endAngle: (float)endAngle
				 clockwise: (BOOL)clockwise
{
  float startAngle_rad, endAngle_rad, diff;
  NSPoint p0, p1, p2, p3;

  /* We use the Postscript prescription for managing the angles and
     drawing the arc.  See the documentation for `arc' and `arcn' in
     the Postscript Reference. */

  if (clockwise)
    {
      /* This modification of the angles is the postscript
         prescription. */
      while (startAngle < endAngle)
        endAngle -= 360;

      /* This is used when we draw a clockwise quarter of
	 circumference.  By adding diff at the starting angle of the
	 quarter, we get the ending angle.  diff is negative because
	 we draw clockwise. */
      diff = - PI / 2;
    }
  else
    {
      /* This modification of the angles is the postscript
         prescription. */
      while (endAngle < startAngle)
        endAngle += 360;

      /* This is used when we draw a counterclockwise quarter of
	 circumference.  By adding diff at the starting angle of the
	 quarter, we get the ending angle.  diff is positive because
	 we draw counterclockwise. */
      diff = PI / 2;
    }

  /* Convert the angles to radians */
  startAngle_rad = PI * startAngle / 180;
  endAngle_rad = PI * endAngle / 180;

  /* Start point */
  p0 = NSMakePoint (center.x + radius * cos (startAngle_rad), 
		    center.y + radius * sin (startAngle_rad));
  if ([self elementCount] == 0)
    {
      [self moveToPoint: p0];
    }
  else
    {
      NSPoint ps = [self currentPoint];
      
      if (p0.x != ps.x  ||  p0.y != ps.y)
	{
	  [self lineToPoint: p0];
	}
    }
  
  while ((clockwise) ? (startAngle_rad > endAngle_rad) 
	 : (startAngle_rad < endAngle_rad))
    {
    /* Add a quarter circle */
    if ((clockwise) ? (startAngle_rad + diff >= endAngle_rad) 
	: (startAngle_rad + diff <= endAngle_rad))
      {
	float sin_start = sin (startAngle_rad);
	float cos_start = cos (startAngle_rad);
	float sign = (clockwise) ? -1.0 : 1.0;
	
	p1 = NSMakePoint (center.x 
                           + radius * (cos_start - KAPPA * sin_start * sign), 
			  center.y 
                           + radius * (sin_start + KAPPA * cos_start * sign));
	p2 = NSMakePoint (center.x 
                           + radius * (-sin_start * sign + KAPPA * cos_start),
			  center.y 
                           + radius * (cos_start * sign + KAPPA * sin_start));
	p3 = NSMakePoint (center.x + radius * (-sin_start * sign),
			  center.y + radius *   cos_start * sign);
	
	[self curveToPoint: p3  controlPoint1: p1  controlPoint2: p2];
	startAngle_rad += diff;
      }
    else
      {
	/* Add the missing bit
	 * We require that the arc be less than a semicircle.
	 * The arc may go either clockwise or counterclockwise.
	 * The approximation is a very simple one: a single curve
	 * whose middle two control points are a fraction F of the way
	 * to the intersection of the tangents, where
	 *      F = (4/3) / (1 + sqrt (1 + (d / r)^2))
	 * where r is the radius and d is the distance from either tangent
	 * point to the intersection of the tangents. This produces
	 * a curve whose center point, as well as its ends, lies on
	 * the desired arc.
	 */
	NSPoint ps = [self currentPoint];
	/* tangent is the tangent of half the angle */
	float tangent = tan ((endAngle_rad - startAngle_rad) / 2);
	/* trad is the distance from either tangent point to the
	   intersection of the tangents */
	float trad = radius * tangent;
	/* pt is the intersection of the tangents */
	NSPoint pt = NSMakePoint (ps.x - trad * sin (startAngle_rad),
				  ps.y + trad * cos (startAngle_rad));
	/* This is F - in this expression we need to compute 
	   (trad/radius)^2, which is simply tangent^2 */
	float f = (4.0 / 3.0) / (1.0 + sqrt (1.0 +  (tangent * tangent)));
	
	p1 = NSMakePoint (ps.x + (pt.x - ps.x) * f, ps.y + (pt.y - ps.y) * f);
	p3 = NSMakePoint(center.x + radius * cos (endAngle_rad),
			 center.y + radius * sin (endAngle_rad));
	p2 = NSMakePoint (p3.x + (pt.x - p3.x) * f, p3.y + (pt.y - p3.y) * f);
	[self curveToPoint: p3  controlPoint1: p1  controlPoint2: p2];
	break;
      }
  }
}

- (void) appendBezierPathWithArcWithCenter: (NSPoint)center  
				    radius: (float)radius
				startAngle: (float)startAngle
				  endAngle: (float)endAngle
{
  [self appendBezierPathWithArcWithCenter: center  radius: radius
	startAngle: startAngle  endAngle: endAngle  clockwise: NO];
}

- (void) appendBezierPathWithArcFromPoint: (NSPoint)point1
				  toPoint: (NSPoint)point2
				   radius: (float)radius
{
  float x1, y1;
  float dx1, dy1, dx2, dy2;
  float l, a1, a2;
  NSPoint p;

  p = [self currentPoint];
 
  x1 = point1.x;
  y1 = point1.y;
  dx1 = p.x - x1;
  dy1 = p.y - y1;
  
  l= dx1*dx1 + dy1*dy1;
  if (l <= 0)
    {
      [self lineToPoint: point1];
      return;
    }
  l = 1/sqrt(l);
  dx1 *= l;
  dy1 *= l;
  
  dx2 = point2.x - x1;
  dy2 = point2.y - y1;

  l = dx2*dx2 + dy2*dy2;
  if (l <= 0)
    {
      [self lineToPoint: point1];
      return;
    }
	
  l = 1/sqrt(l);
  dx2 *= l; 
  dy2 *= l;
  
  l = dx1*dx2 + dy1*dy2;
  if (l < -0.999)
    {
      [self lineToPoint: point1];
      return;
    }

  l = radius/sin(acos(l));
  p.x = x1 + (dx1 + dx2)*l;
  p.y = y1 + (dy1 + dy2)*l;

  if (dx1 < -1)
    a1 = 180;
  else if (dx1 > 1)
    a1 = 0;
  else
    a1 = acos(dx1)/PI*180;
  if (dy1 < 0)
    {   
      a1 = -a1;
    }

  if (dx2 < -1)
    a2 = 180;
  else if (dx2 > 1)
    a2 = 0;
  else
    a2 = acos(dx2)/PI*180;
  if (dy2 < 0)
    {   
      a2 = -a2;
    }

  l = dx1*dy2 - dx2*dy1;
  if (l < 0)
    {
      a2 = a2 - 90;
      a1 = a1 + 90;
      [self appendBezierPathWithArcWithCenter: p  
	    radius: radius
	    startAngle: a1  
	    endAngle: a2  
	    clockwise: NO];
    }
  else
    {
      a2 = a2 + 90;
      a1 = a1 - 90;
      [self appendBezierPathWithArcWithCenter: p  
	    radius: radius
	    startAngle: a1  
	    endAngle: a2  
	    clockwise: YES];
    }
}

- (void)appendBezierPathWithGlyph:(NSGlyph)glyph inFont:(NSFont *)font
{
  [[font fontInfo] appendBezierPathWithGlyphs: &glyph
    count: 1
    toBezierPath: self];
}

- (void)appendBezierPathWithGlyphs:(NSGlyph *)glyphs 
			     count:(int)count
			    inFont:(NSFont *)font
{
  [[font fontInfo] appendBezierPathWithGlyphs: glyphs
    count: count
    toBezierPath: self];
}

- (void)appendBezierPathWithPackedGlyphs:(const char *)packedGlyphs
{
  // TODO
}

//
// Hit detection  
// 
/*
 * Return the contribution of a path segment from start to end to 
 * the containsPoint method for point.
 */
static 
int contribution(NSPoint point, float dir, NSPoint start, NSPoint end, BOOL *hit)
{
  double t;
  double len;
  double a;

  start.x -= point.x;
  start.y -= point.y;
  len = sqrt(start.x * start.x +  start.y * start.y);
  a = atan2(start.y, start.x);
  start.x = cos(a - dir) * len;
  start.y = sin(a - dir) * len;
  end.x -= point.x;
  end.y -= point.y;
  len = sqrt(end.x * end.x +  end.y * end.y);
  a = atan2(end.y, end.x);
  end.x = cos(a - dir) * len;
  end.y = sin(a - dir) * len;

  if ((start.y == 0) || (end.y == 0))
    {
      *hit = YES;
      return 0;
    }
  *hit = NO;

  // Both point on the same half plain
  if ((start.y < 0) == (end.y < 0))
    {
      return 0;
    }
  
  // Does the line hit the coordinate line on the positive side?
  t = (0 - start.y) * (end.x - start.x) / (end.y - start.y) + start.x;

  if (t > 0)
    {
      if (start.y > 0)
	  return 1;
      else 
	  return -1;
    }
  else 
    {
      if (t == 0)
	*hit = YES;
      return 0;
    }
}

- (int) contributionToContains: (NSPoint)point
{
  int i;
  // Full total of contribution
  int sum = 0;
  // running total for the current subpath
  int sub = 0;
  NSBezierPathElement type;
  NSPoint p, pts[3];
  NSPoint first_p, last_p;
  int count = [self elementCount];
  BOOL first = YES;
  float dir = 2 * PI * rand() / RAND_MAX; 
  BOOL hit;

  for(i = 0; i < count; i++) 
    {
      type = [self elementAtIndex: i associatedPoints: pts];
      
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	      // Was the last sub path closed without close?
	      if (!first && NSEqualPoints(first_p, last_p))
	        {
		  sum += sub;
		}
	      sub = 0;
	      first_p = last_p = pts[0];
	      first = NO;
	      break;
	  case NSLineToBezierPathElement:
	      p = pts[0];
	      if (first)
	        {
		  first_p = last_p = p;
		  first = NO;
		}
	      else
	        {
		  sub += contribution(point, dir, last_p, p, &hit);
		  if (hit)
		    return 0;
		  last_p = p;
		}
	      break;
	  case NSCurveToBezierPathElement:
	      // Not possible as flattend!
	      break;
	  case NSClosePathBezierPathElement:
	      sub +=  contribution(point, dir, last_p, first_p, &hit);
	      if (hit)
		return 0;
	      sum += sub;
	      sub = 0;
	      last_p = first_p;
	      first = YES;
	      break;
	  default:
	      break;
	}
    }  

  /*
   * Check if the last sub path was closed.
   * It wont do any harm, if there was only one point in the subpath, 
   * as this will result in a subtotal of 0.
   */
  if (!first && NSEqualPoints(first_p, last_p))
    {
      sum += sub;
    }
  
  return sum;
}

- (BOOL)containsPoint:(NSPoint)point
{
  int sum;

  if(![self elementCount])
    return NO;

  if (!NSPointInRect(point, [self bounds]))
    return NO;

  sum = [[self bezierPathByFlatteningPath] contributionToContains: point];
  if ([self windingRule] == NSNonZeroWindingRule)
    {
      if (sum == 0)
	return NO;
      else	
	return YES;
    }
  else 
    {
      if ((sum % 2) == 0)
	return NO;
      else	
	return YES;
    }
}

//
// Caching paths 
// 
//
// Caching
// 
- (BOOL)cachesBezierPath
{
  return _cachesBezierPath;
}

- (void)setCachesBezierPath:(BOOL)flag
{
  _cachesBezierPath = flag;

  if(!flag)
    INVALIDATE_CACHE();
}

//
// NSCoding protocol
//
- (void)encodeWithCoder:(NSCoder *)aCoder
{
  NSBezierPathElement type;
  NSPoint pts[3];
  int i, count;
  float f;

  f = [self lineWidth];
  [aCoder encodeValueOfObjCType: @encode(float) at: &f];
  i = [self lineCapStyle];
  [aCoder encodeValueOfObjCType: @encode(int) at: &i];
  i = [self lineJoinStyle];
  [aCoder encodeValueOfObjCType: @encode(int) at: &i];
  i = [self windingRule];
  [aCoder encodeValueOfObjCType: @encode(int) at: &i];
  [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_cachesBezierPath];

  count = [self elementCount];
  [aCoder encodeValueOfObjCType: @encode(int) at: &count];
  
  for(i = 0; i < count; i++) 
    {
      type = [self elementAtIndex: i associatedPoints: pts];
      [aCoder encodeValueOfObjCType: @encode(NSBezierPathElement) at: &type];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	  case NSLineToBezierPathElement:
	      [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
	      break;
	  case NSCurveToBezierPathElement:
	      [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
	      [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[1]];
	      [aCoder encodeValueOfObjCType: @encode(NSPoint) at: &pts[2]];
	      break;
	  case NSClosePathBezierPathElement:
	      break;
	  default:
	      break;
	}
    }
}

- (id)initWithCoder:(NSCoder *)aCoder
{
  NSBezierPathElement type;
  NSPoint pts[3];
  int i, count;
  float f;

  // We have to init the place to store the elements
  [self init];

  [aCoder decodeValueOfObjCType: @encode(float) at: &f];
  [self setLineWidth: f];
  [aCoder decodeValueOfObjCType: @encode(int) at: &i];
  [self setLineCapStyle: i];
  [aCoder decodeValueOfObjCType: @encode(int) at: &i];
  [self setLineJoinStyle: i];
  [aCoder decodeValueOfObjCType: @encode(int) at: &i];
  [self setWindingRule: i];
  [aCoder decodeValueOfObjCType: @encode(BOOL) at: &_cachesBezierPath];
  _cacheImage = nil;
  _shouldRecalculateBounds = YES;

  [aCoder decodeValueOfObjCType: @encode(int) at: &count];

  for(i = 0; i < count; i++) 
    {
      [aCoder decodeValueOfObjCType: @encode(NSBezierPathElement) at: &type];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	      [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
	      [self moveToPoint: pts[0]];
	  case NSLineToBezierPathElement:
	      [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
	      [self lineToPoint: pts[0]];
	      break;
	  case NSCurveToBezierPathElement:
	      [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[0]];
	      [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[1]];
	      [aCoder decodeValueOfObjCType: @encode(NSPoint) at: &pts[2]];
	      [self curveToPoint: pts[0] controlPoint1: pts[1] controlPoint2: pts[2]];
	      break;
	  case NSClosePathBezierPathElement:
	      [self closePath];
	      break;
	  default:
	      break;
	}
    }

  return self;
}

//
// NSCopying Protocol
//
- (id)copyWithZone:(NSZone *)zone
{
  NSBezierPath *path = (NSBezierPath*)NSCopyObject (self, 0, zone);

  if(_cachesBezierPath && _cacheImage)
      path->_cacheImage = [_cacheImage copy];

  if (_dash_pattern != NULL)
    {
      float *pattern = NSZoneMalloc(zone, _dash_count * sizeof(float));

      memcpy(pattern, _dash_pattern, _dash_count * sizeof(float));
      _dash_pattern = pattern;
    }

  return path;
}

@end


@implementation NSBezierPath (PrivateMethods)

- (void) _invalidateCache
{
  _shouldRecalculateBounds = YES;
  DESTROY(_cacheImage);
}

- (void)_recalculateBounds
{
  NSBezierPathElement type;
  NSPoint p, last_p;
  NSPoint pts[3];
  // This will compute three intermediate points per curve
  double x, y, t, k = 0.25;
  float maxx, minx, maxy, miny;
  float cpmaxx, cpminx, cpmaxy, cpminy;	
  int i, count;
  BOOL first = YES;
  
  count = [self elementCount];
  if(!count)
    {
      _bounds = NSZeroRect;
      _controlPointBounds = NSZeroRect;
      return;
    }

  // Some big starting values
  maxx = maxy = cpmaxx = cpmaxy = -1E9;
  minx = miny = cpminx = cpminy = 1E9;

  for(i = 0; i < count; i++) 
    {
      type = [self elementAtIndex: i associatedPoints: pts];
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	      last_p = pts[0];
	      // NO BREAK
	  case NSLineToBezierPathElement:
	      if (first)
	        {
		  maxx = minx = cpmaxx = cpminx = pts[0].x;
		  maxy = miny = cpmaxy = cpminy = pts[0].y;
		  last_p = pts[0];
		  first = NO;
		}
	      else
	      {
	        if(pts[0].x > maxx) maxx = pts[0].x;
		if(pts[0].x < minx) minx = pts[0].x;
		if(pts[0].y > maxy) maxy = pts[0].y;
		if(pts[0].y < miny) miny = pts[0].y;
		
		if(pts[0].x > cpmaxx) cpmaxx = pts[0].x;
		if(pts[0].x < cpminx) cpminx = pts[0].x;
		if(pts[0].y > cpmaxy) cpmaxy = pts[0].y;
		if(pts[0].y < cpminy) cpminy = pts[0].y;
	      }

	      p = pts[0];
	      break;

	  case NSCurveToBezierPathElement:
	      if (first)
	        {
		  maxx = minx = cpmaxx = cpminx = pts[0].x;
		  maxy = miny = cpmaxy = cpminy = pts[0].y;
		  p = last_p = pts[0];
		  first = NO;
		}

	      if(pts[2].x > maxx) maxx = pts[2].x;
	      if(pts[2].x < minx) minx = pts[2].x;
	      if(pts[2].y > maxy) maxy = pts[2].y;
	      if(pts[2].y < miny) miny = pts[2].y;
	      
	      if(pts[0].x > cpmaxx) cpmaxx = pts[0].x;
	      if(pts[0].x < cpminx) cpminx = pts[0].x;
	      if(pts[0].y > cpmaxy) cpmaxy = pts[0].y;
	      if(pts[0].y < cpminy) cpminy = pts[0].y;
	      if(pts[1].x > cpmaxx) cpmaxx = pts[1].x;
	      if(pts[1].x < cpminx) cpminx = pts[1].x;
	      if(pts[1].y > cpmaxy) cpmaxy = pts[1].y;
	      if(pts[1].y < cpminy) cpminy = pts[1].y;
	      if(pts[2].x > cpmaxx) cpmaxx = pts[2].x;
	      if(pts[2].x < cpminx) cpminx = pts[2].x;
	      if(pts[2].y > cpmaxy) cpmaxy = pts[2].y;
	      if(pts[2].y < cpminy) cpminy = pts[2].y;
	      
	      for(t = k; t <= 1+k; t += k) 
	        {
		  x = (p.x+t*(-p.x*3+t*(3*p.x-p.x*t)))+
		      t*(3*pts[0].x+t*(-6*pts[0].x+pts[0].x*3*t))+
		      t*t*(pts[1].x*3-pts[1].x*3*t)+pts[2].x*t*t*t;
		  y = (p.y+t*(-p.y*3+t*(3*p.y-p.y*t)))+
		      t*(3*pts[0].y+t*(-6*pts[0].y+pts[0].y*3*t))+
		      t*t*(pts[1].y*3-pts[1].y*3*t)+pts[2].y*t*t*t;
		  
		  if(x > cpmaxx) cpmaxx = x;
		  if(x < cpminx) cpminx = x;
		  if(y > cpmaxy) cpmaxy = y;
		  if(y < cpminy) cpminy = y;
		}

	      p = pts[2];
	      break;

	  case NSClosePathBezierPathElement:
	      // This does not add to the bounds, but changes the current point
	      p = last_p;
	      break;
	  default:
	      break;
	}
    }
  
  _bounds = NSMakeRect(minx, miny, maxx - minx, maxy - miny);
  _controlPointBounds = NSMakeRect(cpminx, cpminy, cpmaxx - cpminx, cpmaxy - cpminy);
  _shouldRecalculateBounds = NO;
}

@end


typedef struct _PathElement
{
  NSBezierPathElement type;
  NSPoint points[3];
} PathElement;

//#define GSUNION_TYPES GSUNION_OBJ
#define GSI_ARRAY_TYPES       0
#define GSI_ARRAY_TYPE	PathElement

#define GSI_ARRAY_NO_RETAIN
#define GSI_ARRAY_NO_RELEASE

#ifdef GSIArray
#undef GSIArray
#endif
#include <GNUstepBase/GSIArray.h>

@interface GSBezierPath : NSBezierPath
{
  GSIArray pathElements;
  BOOL flat;
}

@end

@implementation GSBezierPath

- (id)init
{
  NSZone *zone;

  self = [super init];
  zone = GSObjCZone(self);
  pathElements = NSZoneMalloc(zone, sizeof(GSIArray_t));
  GSIArrayInitWithZoneAndCapacity(pathElements, zone, 8);
  flat = YES;

  return self;
}

- (void)dealloc
{
  GSIArrayEmpty(pathElements);
  NSZoneFree(GSObjCZone(self), pathElements);
  [super dealloc];
}

//
// Path construction
//
- (void)moveToPoint:(NSPoint)aPoint
{
  PathElement elem;
  
  elem.type = NSMoveToBezierPathElement;
  elem.points[0] = aPoint;
  GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
  INVALIDATE_CACHE();
}

- (void)lineToPoint:(NSPoint)aPoint
{
  PathElement elem;
  
  elem.type = NSLineToBezierPathElement;
  elem.points[0] = aPoint;
  GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
  INVALIDATE_CACHE();
}

- (void) curveToPoint: (NSPoint)aPoint 
	controlPoint1: (NSPoint)controlPoint1
	controlPoint2: (NSPoint)controlPoint2
{
  PathElement elem;
  
  elem.type = NSCurveToBezierPathElement;
  elem.points[0] = controlPoint1;
  elem.points[1] = controlPoint2;
  elem.points[2] = aPoint;
  GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
  flat = NO;

  INVALIDATE_CACHE();
}

- (void)closePath
{
  PathElement elem;

  elem.type = NSClosePathBezierPathElement;
  GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
  INVALIDATE_CACHE();
}

- (void)removeAllPoints
{
  GSIArrayRemoveAllItems(pathElements);
  INVALIDATE_CACHE();
}

//
// Elements
//
- (int)elementCount
{
  return GSIArrayCount(pathElements);
}

- (NSBezierPathElement)elementAtIndex:(int)index
		     associatedPoints:(NSPoint *)points
{
  PathElement elm = GSIArrayItemAtIndex(pathElements, index).ext;
  NSBezierPathElement type = elm.type;
	
  if (points != NULL) 
    {
      if(type == NSMoveToBezierPathElement || type == NSLineToBezierPathElement) 
        {
	  points[0] = elm.points[0];
	} 
      else if(type == NSCurveToBezierPathElement) 
        {
	  points[0] = elm.points[0];
	  points[1] = elm.points[1];
	  points[2] = elm.points[2];
	}
    }
  
  return type;
}

- (void)setAssociatedPoints:(NSPoint *)points atIndex:(int)index
{
  PathElement elm = GSIArrayItemAtIndex(pathElements, index).ext;
  NSBezierPathElement type = elm.type;
  
  switch(type) 
    {
      case NSMoveToBezierPathElement:
      case NSLineToBezierPathElement:
	  elm.points[0] = points[0];
	  break;
      case NSCurveToBezierPathElement:
	  elm.points[0] = points[0];
	  elm.points[1] = points[1];
	  elm.points[2] = points[2];
	  break;
      case NSClosePathBezierPathElement:
	  break;
      default:
	  break;
    }

  GSIArraySetItemAtIndex(pathElements, (GSIArrayItem)elm, index);
  INVALIDATE_CACHE();
}

//
// Path modifications.
//
- (NSBezierPath *)bezierPathByFlatteningPath
{
  if (flat)
    return self;

  return [super bezierPathByFlatteningPath];
}

- (void) transformUsingAffineTransform: (NSAffineTransform *)transform
{
  NSBezierPathElement type;
  int i, count;
  PathElement *elments = (PathElement *)GSIArrayItems(pathElements);
  SEL transformPointSel = @selector(transformPoint:); 
  NSPoint (*transformPointImp)(NSAffineTransform*, SEL, NSPoint);

  transformPointImp = (NSPoint (*)(NSAffineTransform*, SEL, NSPoint))
      [transform methodForSelector: transformPointSel];

  count = GSIArrayCount(pathElements);
  for(i = 0; i < count; i++) 
    {
      type = elments[i].type;
      switch(type) 
        {
	  case NSMoveToBezierPathElement:
	  case NSLineToBezierPathElement:
	      elments[i].points[0] = (*transformPointImp)(transform,  
							  transformPointSel, 
							  elments[i].points[0]);
	      break;
	  case NSCurveToBezierPathElement:
	      elments[i].points[0] = (*transformPointImp)(transform,  
							  transformPointSel, 
							  elments[i].points[0]);
	      elments[i].points[1] = (*transformPointImp)(transform,  
							  transformPointSel, 
							  elments[i].points[1]);
	      elments[i].points[2] = (*transformPointImp)(transform,  
							  transformPointSel, 
							  elments[i].points[2]);
	      break;
	  case NSClosePathBezierPathElement:
	      break;
	  default:
	      break;
	}
    }
  INVALIDATE_CACHE();
}

- (void) appendBezierPath: (NSBezierPath *)aPath
{
  PathElement elem;
  int i, count;

  if (![aPath isKindOfClass: isa])
    {
      [super appendBezierPath: aPath];
      return;
    }

  flat = flat && ((GSBezierPath*)aPath)->flat;
  count = [aPath elementCount];

  for (i = 0; i < count; i++)
    {
      elem = GSIArrayItemAtIndex(((GSBezierPath*)aPath)->pathElements, i).ext;
      GSIArrayAddItem(pathElements, (GSIArrayItem)elem);
    }
  INVALIDATE_CACHE();
}

//
// NSCopying Protocol
//
- (id)copyWithZone:(NSZone *)zone
{
  GSBezierPath *path = [super copyWithZone: zone];
	
  path->pathElements = GSIArrayCopyWithZone(pathElements, zone);

  return path;
}

@end // GSBezierPath


static void flatten(NSPoint coeff[], float flatness, NSBezierPath *path)
{
  // Check if the Bezier path defined by the four points has the given flatness.
  // If not split it up in the middle and recurse. 
  // Otherwise add the end point to the path.
  BOOL flat = YES;

  // This criteria for flatness is based on code from Libart which has the 
  // following copyright:
/* Libart_LGPL - library of basic graphic primitives
 * Copyright (C) 1998 Raph Levien
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

  double x1_0, y1_0;
  double x3_2, y3_2;
  double x3_0, y3_0;
  double z3_0_dot;
  double z1_dot, z2_dot;
  double z1_perp, z2_perp;
  double max_perp_sq;

  x3_0 = coeff[3].x - coeff[0].x;
  y3_0 = coeff[3].y - coeff[0].y;
  x3_2 = coeff[3].x - coeff[2].x;
  y3_2 = coeff[3].y - coeff[2].y;
  x1_0 = coeff[1].x - coeff[0].x;
  y1_0 = coeff[1].y - coeff[0].y;
  z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0;

  if (z3_0_dot < 0.001)
    flat = YES;
  else
    {
      max_perp_sq = flatness * flatness * z3_0_dot;

      z1_perp = y1_0 * x3_0 - x1_0 * y3_0;
      if (z1_perp * z1_perp > max_perp_sq)
	flat = NO;
      else
        {
	  z2_perp = y3_2 * x3_0 - x3_2 * y3_0;
	  if (z2_perp * z2_perp > max_perp_sq)
	      flat = NO;
	  else
	    {
	      z1_dot = x1_0 * x3_0 + y1_0 * y3_0;
	      if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq)
		flat = NO;
	      else
	        {
		  z2_dot = x3_2 * x3_0 + y3_2 * y3_0;
		  if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq)
		      flat = NO;
		  else
		    {
		      if ((z1_dot + z1_dot > z3_0_dot) ||
			  (z2_dot + z2_dot > z3_0_dot))
			flat = NO;
		    }
		}
	    }
	}
    }

  if (!flat)
    {
      NSPoint bleft[4], bright[4];
	
      bleft[0] = coeff[0];
      bleft[1].x = (coeff[0].x + coeff[1].x) / 2;
      bleft[1].y = (coeff[0].y + coeff[1].y) / 2;
      bleft[2].x = (coeff[0].x + 2*coeff[1].x + coeff[2].x) / 4;
      bleft[2].y = (coeff[0].y + 2*coeff[1].y + coeff[2].y) / 4;
      bleft[3].x = (coeff[0].x + 3*(coeff[1].x + coeff[2].x) + coeff[3].x) / 8;
      bleft[3].y = (coeff[0].y + 3*(coeff[1].y + coeff[2].y) + coeff[3].y) / 8;
      bright[0].x =  bleft[3].x;
      bright[0].y =  bleft[3].y;
      bright[1].x = (coeff[3].x + 2*coeff[2].x + coeff[1].x) / 4;
      bright[1].y = (coeff[3].y + 2*coeff[2].y + coeff[1].y) / 4;
      bright[2].x = (coeff[3].x + coeff[2].x) / 2;
      bright[2].y = (coeff[3].y + coeff[2].y) / 2;
      bright[3] = coeff[3];

      flatten(bleft, flatness, path);
      flatten(bright, flatness, path);
    }
  else
    {
      //[path lineToPoint: coeff[1]];
      //[path lineToPoint: coeff[2]];
      [path lineToPoint: coeff[3]];
    }
}

