/* 
 NSBezierPath.m
 
 Bezier path drawing class
 
 Copyright (C) 1998 Free Software Foundation, Inc.
 
 Author:  Enrico Sersale <enrico@imago.ro>
 Date:    Dec 1999
 Author:  Fred Kiefer <FredKiefer@gmx.de>
 Date:    January 2001
 
 This file is part of the mySTEP Library and is provided
 under the terms of the GNU Library General Public License.
 
 */ 

#import <AppKit/NSAffineTransform.h>
#import <AppKit/NSBezierPath.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSGraphicsContext.h>

#import "NSBackendPrivate.h"

#ifndef PI
#define PI 3.1415926535897932384626433
#endif

#define PMAX  10000
#define KAPPA 0.5522847498				// magic number = 4 *(sqrt(2) -1)/3	

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

static float __defaultLineWidth = 1.0;
static float __defaultFlatness = 1.0;
static float __defaultMiterLimit = 10.0;
static NSLineJoinStyle __defaultLineJoinStyle = NSMiterLineJoinStyle;
static NSLineCapStyle __defaultLineCapStyle = NSButtLineCapStyle;
static NSWindingRule __defaultWindingRule = NSNonZeroWindingRule;


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


@implementation NSBezierPath

+ (NSBezierPath *) bezierPath
{
	return [[self new] autorelease];
}

+ (NSBezierPath *) bezierPathWithRect:(NSRect)aRect
{
	// FIXME: return instance of _NSRectBezierPath
	// or we define a private _NSRectBezierPathElement defining two corner points
	// PDF and X11 can use that directly do render rects
	
	NSBezierPath *path = [[self new] autorelease];
	NSPoint p;
	
	[path moveToPoint: aRect.origin];
	p.x = NSMaxX(aRect);
	p.y = aRect.origin.y;
	[path lineToPoint: p];
	p.y = NSMaxY(aRect);
	[path lineToPoint: p];
	p.x = NSMinX(aRect);
	[path lineToPoint: p];
	[path closePath];
	
	return path;
}

+ (NSBezierPath *) bezierPathWithOvalInRect:(NSRect)rect
{
	NSBezierPath *path = [NSBezierPath bezierPath];
	NSPoint p, p1, p2;
	double originx = rect.origin.x;
	double originy = rect.origin.y;
	double width = rect.size.width;
	double height = rect.size.height;
	double hdiff = width / 2 * KAPPA;
	double vdiff = height / 2 * KAPPA;
	
	p = NSMakePoint(originx + width / 2, originy + height);
	[path moveToPoint: p];
	
	p = NSMakePoint(originx, originy + height / 2);
	p1 = NSMakePoint(originx + width / 2 - hdiff, originy + height);
	p2 = NSMakePoint(originx, originy + height / 2 + vdiff);
	[path 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);
	[path 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);
	[path 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);
	[path curveToPoint: p controlPoint1: p1 controlPoint2: p2];	
	
	return path;
}

// this is a special case of _drawRoundedBezel:

+ (NSBezierPath *) _bezierPathWithBoxBezelInRect:(NSRect) borderRect radius:(float) radius
{
	NSBezierPath *b=[NSBezierPath new];
	borderRect.size.width-=1.0;
	borderRect.size.height-=1.0;	// draw inside
	[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(borderRect)+radius, NSMinY(borderRect)+radius)
								  radius:radius
							  startAngle:270.0
								endAngle:180.0
							   clockwise:YES];
	[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(borderRect)+radius, NSMaxY(borderRect)-radius)
								  radius:radius
							  startAngle:180.0
								endAngle:90.0
							   clockwise:YES];
	[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(borderRect)-radius, NSMaxY(borderRect)-radius)
								  radius:radius
							  startAngle:90.0
								endAngle:0.0
							   clockwise:YES];
	[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(borderRect)-radius, NSMinY(borderRect)+radius)
								  radius:radius
							  startAngle:0.0
								endAngle:270.0
							   clockwise:YES];
	[b closePath];
	return b;
}

// this is a special case of _drawRoundedBezel:

+ (NSBezierPath *) _bezierPathWithRoundedBezelInRect:(NSRect) borderRect vertical:(BOOL) flag;	// box with halfcircular rounded ends
{
	NSBezierPath *p=[NSBezierPath bezierPath];
	NSPoint point=borderRect.origin;
	float radius;
	borderRect.size.width-=1.0;
	borderRect.size.height-=1.0;
	if(flag)
		{ // vertical
		radius=borderRect.size.width/2.0;
		point.x+=radius;
		point.y+=radius;
		radius-=1.0;
		[p appendBezierPathWithArcWithCenter:point radius:radius startAngle:180.0 endAngle:360.0];	// bottom half-circle
		point.y+=borderRect.size.height-borderRect.size.width;
		[p appendBezierPathWithArcWithCenter:point radius:radius startAngle:0.0 endAngle:180.0];	// line to first point and top halfcircle
		}
	else
		{ // horizontal
		radius=borderRect.size.height/2.0;
		point.x+=radius;
		point.y+=radius;
		radius-=1.0;
		[p appendBezierPathWithArcWithCenter:point radius:radius startAngle:90.0 endAngle:270.0];	// left half-circle
		point.x+=borderRect.size.width-borderRect.size.height;
		[p appendBezierPathWithArcWithCenter:point radius:radius startAngle:270.0 endAngle:90.0];	// line to first point and right halfcircle
		}
	[p closePath];
	return p;
}

// rename to _drawSegmentedBezel
// add backgroundColor parameter (for the default if not enabled)
// add radius parameter - then we can also use it to draw the standard round button

+ (void) _drawRoundedBezel:(NSRoundedBezelSegments) border inFrame:(NSRect) frame enabled:(BOOL) enabled selected:(BOOL) selected highlighted:(BOOL) highlighted radius:(float) radius;
{
	NSColor *background;
	NSBezierPath *b=[NSBezierPath new];
	if(border&NSRoundedBezelLeftSegment)
		{ // left side shaped
		[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(frame)+radius, NSMinY(frame)+radius)
									  radius:radius
								  startAngle:270.0
									endAngle:180.0
								   clockwise:YES];
		[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(frame)+radius, NSMaxY(frame)-1.0-radius)
									  radius:radius
								  startAngle:180.0
									endAngle:90.0
								   clockwise:YES];
		}
	else
		{ // left vertical
		[b moveToPoint:NSMakePoint(NSMinX(frame), NSMinY(frame))];
		[b lineToPoint:NSMakePoint(NSMinX(frame), NSMaxY(frame)-1.0)];
		}
	if(border&NSRoundedBezelRightSegment)
		{ // right side shaped
		[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(frame)-radius, NSMaxY(frame)-1.0-radius)
									  radius:radius
								  startAngle:90.0
									endAngle:0.0
								   clockwise:YES];
		[b appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(frame)-radius, NSMinY(frame)+radius)
									  radius:radius
								  startAngle:0.0
									endAngle:270.0
								   clockwise:YES];
		}
	else
		{ // right vertical
		[b lineToPoint:NSMakePoint(NSMaxX(frame), NSMaxY(frame)-1.0)];
		[b lineToPoint:NSMakePoint(NSMaxX(frame), NSMinY(frame))];
		}
	[b closePath];
	if(enabled)
		{
		if(selected)
			{
			if(highlighted)
				background=[NSColor controlShadowColor];
			else
				background=[NSColor selectedControlColor];
			}
		else
			background=[NSColor controlColor];
		}
	else
		background=[NSColor controlBackgroundColor];
	[background set];
	[b fill];	// fill background
	[[NSColor blackColor] set];
	[b stroke];	// stroke border line
	[b release];
}

+ (void) fillRect:(NSRect)aRect	// Immediate mode drawing
{
	NSBezierPath *bp;
	if(NSIsEmptyRect(aRect))
		return;
	bp=[NSBezierPath bezierPathWithRect:aRect];
	[bp setWindingRule:NSEvenOddWindingRule];
	[bp fill];
}

+ (void) strokeRect:(NSRect)aRect
{
	if(NSIsEmptyRect(aRect))
		return;
	[[NSBezierPath bezierPathWithRect:aRect] stroke];
}

+ (void) clipRect:(NSRect)aRect
{
	NSBezierPath *bp=[NSBezierPath bezierPathWithRect:aRect];
	[bp setWindingRule:NSEvenOddWindingRule];
// FIXME: should be	[bp addClip];
	[bp setClip];
}

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

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

+ (void) setDefaultFlatness:(float)flatness	{ __defaultFlatness = flatness; }
+ (void) setDefaultLineWidth:(float)lnWidth	{ __defaultLineWidth = lnWidth; }
+ (void) setDefaultMiterLimit:(float)limit	{ __defaultMiterLimit = limit; }
+ (void) setDefaultWindingRule:(NSWindingRule)windingRule		{ __defaultWindingRule = windingRule; }
+ (void) setDefaultLineCapStyle:(NSLineCapStyle)lineCapStyle	{ __defaultLineCapStyle = lineCapStyle; }
+ (void) setDefaultLineJoinStyle:(NSLineJoinStyle)lineJoinStyle { __defaultLineJoinStyle = lineJoinStyle; }
+ (float) defaultMiterLimit					{ return __defaultMiterLimit; }
+ (float) defaultLineWidth					{ return __defaultLineWidth; }
+ (float) defaultFlatness					{ return __defaultFlatness; }
+ (NSWindingRule) defaultWindingRule		{ return __defaultWindingRule; }
+ (NSLineCapStyle) defaultLineCapStyle		{ return __defaultLineCapStyle; }
+ (NSLineJoinStyle) defaultLineJoinStyle	{ return __defaultLineJoinStyle; }

- (id) init
{
	if((self=[super init]))
		{
		_lineWidth = __defaultLineWidth;
		_flatness = __defaultFlatness;
		_bz.lineCapStyle = __defaultLineCapStyle;
		_bz.lineJoinStyle = __defaultLineJoinStyle;
		_bz.windingRule = __defaultWindingRule;
		_miterLimit = __defaultMiterLimit;
	
		_bPath = objc_malloc((_capacity = 8) * sizeof(PathElement *));
		if(!_bPath)
			{
			[self release];
			return nil;
			}
		_bz.flat = YES;
		}
	return self;
}

- (void) dealloc
{
	[self removeAllPoints];
	objc_free(_bPath);
	if (_dashPattern != NULL)
		objc_free(_dashPattern);
	[super dealloc];
}

- (NSString *) description;
{
	int i;
	NSString *str=@"NSBezierPath:\n";
	for(i=0; i<_count; i++)
		{
		NSPoint points[3];
		switch([self elementAtIndex:i associatedPoints:points])
			{
			case NSMoveToBezierPathElement:
				str=[str stringByAppendingFormat:@"m %@\n", NSStringFromPoint(points[0])];
				break;
			case NSLineToBezierPathElement:
				str=[str stringByAppendingFormat:@"l %@\n", NSStringFromPoint(points[0])];
				break;
			case NSCurveToBezierPathElement:
				str=[str stringByAppendingFormat:@"c %@ %@ %@\n", NSStringFromPoint(points[0]), NSStringFromPoint(points[1]), NSStringFromPoint(points[2])];
				break;
			case NSClosePathBezierPathElement:
				str=[str stringByAppendingFormat:@"h\n"];
				break;
			}
		}
	return str;
}

- (void) moveToPoint:(NSPoint)aPoint				// Path construction
{
	PathElement *e = objc_malloc(sizeof(PathElement));
	
	e->type = NSMoveToBezierPathElement;
	e->points[0] = aPoint;
	if (_count >= _capacity)
		_bPath = objc_realloc(_bPath, (_capacity = 2 * _count + 3) * sizeof(PathElement *));
	_bPath[_count++] = e;
	_bz.shouldRecalculateBounds = YES;
}

- (void) lineToPoint:(NSPoint)aPoint
{
	PathElement *e = objc_malloc(sizeof(PathElement));
	
	e->type = NSLineToBezierPathElement;
	e->points[0] = aPoint;
	if (_count >= _capacity)
		_bPath = objc_realloc(_bPath, (_capacity = 2 * _count + 3) * sizeof(PathElement *));
	_bPath[_count++] = e;
	_bz.shouldRecalculateBounds = YES;
}

- (void) curveToPoint:(NSPoint)aPoint 
		controlPoint1:(NSPoint)controlPoint1
		controlPoint2:(NSPoint)controlPoint2
{
	PathElement *e = objc_malloc(sizeof(PathElement));
	
	e->type = NSCurveToBezierPathElement;
	e->points[0] = controlPoint1;
	e->points[1] = controlPoint2;
	e->points[2] = aPoint;
	if (_count >= _capacity)
		_bPath = objc_realloc(_bPath, (_capacity = 2 * _count + 3) * sizeof(PathElement *));
	_bPath[_count++] = e;

	_bz.flat = NO;
	
	_bz.shouldRecalculateBounds = YES;
}

- (void) closePath
{
	PathElement *e = objc_malloc(sizeof(PathElement));
	e->type = NSClosePathBezierPathElement;
	if (_count >= _capacity)
		_bPath = objc_realloc(_bPath, (_capacity = 2 * _count + 3) * sizeof(PathElement *));
	_bPath[_count++] = e;
	_bz.shouldRecalculateBounds = YES;
}

- (void) removeAllPoints
{
#if 0
	NSLog(@"removeAllPoints[%u capa=%u] %p %@", _count, _capacity, _bPath, self);
#endif
	while(_count > 0)
		{
		_count--;
#if 0
		NSLog(@"remove %u %p", _count, _bPath[_count]);
#endif
		objc_free(_bPath[_count]);
		}
#if 0
	NSLog(@"  cnt=%u", _count);
#endif
	_bz.shouldRecalculateBounds = YES;
#if 0
	NSLog(@"  -> [%u] %@", _count, self);
#endif
}

- (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];
}

- (float) lineWidth								{ return _lineWidth; }
- (float) flatness								{ return _flatness; }
- (float) miterLimit							{ return _miterLimit; }
- (NSLineJoinStyle) lineJoinStyle				{ return _bz.lineJoinStyle; }
- (NSLineCapStyle) lineCapStyle					{ return _bz.lineCapStyle; }
- (NSWindingRule) windingRule					{ return _bz.windingRule; }
- (void) setLineWidth:(float)lineWidth			{ _lineWidth = lineWidth; }
- (void) setFlatness:(float)flatness			{ _flatness = flatness; }
- (void) setLineCapStyle:(NSLineCapStyle)ls		{ _bz.lineCapStyle = ls; }
- (void) setLineJoinStyle:(NSLineJoinStyle)lj	{ _bz.lineJoinStyle = lj; }
- (void) setWindingRule:(NSWindingRule)wr		{ _bz.windingRule = wr; }
- (void) setMiterLimit:(float)limit				{ _miterLimit = limit; }

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

- (void) setLineDash:(const float *)pattern count:(int)count phase:(float)phase
{
	if ((pattern == NULL) || (count == 0))
		{
		if (_dashPattern != NULL)
			objc_free(_dashPattern);
		_dashPattern = NULL;
		_dashCount = 0;
		_dashPhase = 0.0;
		
		return;
		}
	
	if (_dashPattern == NULL)
		_dashPattern = objc_malloc(count * sizeof(float));
	else
		_dashPattern = objc_realloc(_dashPattern, count * sizeof(float));
	
	_dashCount = count;
	_dashPhase = phase;
	memcpy(_dashPattern, pattern, _dashCount * sizeof(float));
}

- (void) stroke
{
	[[NSGraphicsContext currentContext] _stroke:self];
}

- (void) fill
{
	[[NSGraphicsContext currentContext] _fill:self];
}

- (void) addClip
{
	[[NSGraphicsContext currentContext] _addClip:self];
}

- (void) setClip
{
	[[NSGraphicsContext currentContext] _setClip:self];
}

- (NSBezierPath *) bezierPathByFlatteningPath
{
	NSBezierPath *path;
	NSPoint pts[3];
	NSPoint coeff[4];
	NSPoint p, last_p;
	int i;
	BOOL first = YES;
	
	if (_bz.flat)
		return self;	// already flat
	path = [isa bezierPath];	// allocate fresh path
	
	for(i = 0; i < _count; i++)
		{
		switch([self elementAtIndex: i associatedPoints: pts]) 
			{
			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;
			default:
				break;
			}
		}
	
	return path;
}

- (NSBezierPath *) bezierPathByReversingPath
{
	NSBezierPath *path = [isa bezierPath];
	NSBezierPathElement type, last_type = NSMoveToBezierPathElement;
	NSPoint pts[3];
	NSPoint p, cp1, cp2;
	int i, j;
	BOOL closed = NO;
	
	for(i = _count - 1; i >= 0; i--) 
		{
		switch((type = [self elementAtIndex: i associatedPoints: pts])) 
			{
			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:		// FIX ME looks wrong
				for (j = i - 1; j >= 0; j--) // find the first point of segment
					{
					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?
			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;
			default:
				break;
			}
		last_type = type;
		}
	
	if (closed)
		[path closePath];
	
	return self;
}

- (void) transformUsingAffineTransform:(NSAffineTransform *)transform
{
	NSPoint pts[3];
	int i;
	
	for(i = 0; i < _count; i++) 
		switch([self elementAtIndex: i associatedPoints: pts]) 
			{
			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];
			case NSClosePathBezierPathElement:
			default:
				break;
			}
			
	_bz.shouldRecalculateBounds = YES;
}

- (NSPoint) currentPoint
{
	NSPoint points[3];
	int i;
	
	if (!_count) 
		[NSException raise: NSGenericException
					format: @"No current Point in NSBezierPath"];
	
	switch([self elementAtIndex: _count - 1 associatedPoints: points]) 
		{
		case NSMoveToBezierPathElement:
		case NSLineToBezierPathElement:
			return points[0];
			
		case NSCurveToBezierPathElement:
			return points[2];
			
		case NSClosePathBezierPathElement:			// We have to find the last			
			for (i = _count - 2; i >= 0; i--)		// move element and take
				{									// its point
				NSBezierPathElement type = [self elementAtIndex: i
											   associatedPoints: points];
				if (type == NSMoveToBezierPathElement)
					return points[0];
				}
		default:
			break;
		}
	
	return NSZeroPoint;
}

- (NSRect) controlPointBounds
{
	if (_bz.shouldRecalculateBounds)
		[self bounds];
	
	return _controlPointBounds;
}

- (NSRect) bounds
{
	if (_bz.shouldRecalculateBounds)
		{
		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;
		BOOL first = YES;
		
		if(!_count)
			{
			_bounds = NSZeroRect;
			_controlPointBounds = NSZeroRect;
			return NSZeroRect;
			}
		
		maxx = maxy = cpmaxx = cpmaxy = -1E9;		// Some big starting values
		minx = miny = cpminx = cpminy = 1E9;
		
		for(i = 0; i < _count; i++) 
			{
			switch([self elementAtIndex: i associatedPoints: pts]) 
				{
				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:
					p = last_p;							// Changes current point
				default:
					break;
				}	}
		
		_bounds = NSMakeRect(minx, miny, maxx - minx, maxy - miny);
		_controlPointBounds = NSMakeRect(cpminx, cpminy, 
										 cpmaxx - cpminx, cpmaxy - cpminy);
		_bz.shouldRecalculateBounds = NO;
		}
	
	return _bounds;
}

- (BOOL) isEmpty								{ return (_count == 0); }
- (int) elementCount							{ return _count; }

- (NSBezierPathElement) elementAtIndex:(int)index
					  associatedPoints:(NSPoint *)points
{
	PathElement *e;
#if 0
	NSLog(@"elementAtIndex:%d [%u]", index, _count);
#endif
	if (index < 0 || index >= _count)
		[NSException raise: NSRangeException format: @"Bad Index"];

	e = _bPath[index];
	if (points != NULL) 
		{
		NSBezierPathElement t = e->type;
		
		if(t == NSMoveToBezierPathElement || t == NSLineToBezierPathElement) 
			points[0] = e->points[0];
		else if(t == NSCurveToBezierPathElement) 
			{
			points[0] = e->points[0];
			points[1] = e->points[1];
			points[2] = e->points[2];
			}
		}
	
	return e->type;
}

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

- (void) appendBezierPath:(NSBezierPath *)aPath
{
	int i, count = [aPath elementCount];
	
	_bz.flat = _bz.flat && aPath->_bz.flat;
	
	if ((_count + count) >= _capacity)
		{
		_capacity = (2 * _count) + count;
		_bPath = objc_realloc(_bPath, _capacity * sizeof(PathElement *));
		}
	
	for (i = 0; i < count; i++)
		{
		PathElement *e = objc_malloc(sizeof(PathElement));
		
		*e = *(PathElement *)aPath->_bPath[i];
		_bPath[_count++] = e;
		}
	
	_bz.shouldRecalculateBounds = YES;
}

- (void) appendBezierPathWithRect:(NSRect)rect
{
	[self appendBezierPath: [isa bezierPathWithRect: rect]];
}

- (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
{
	[self appendBezierPath: [isa bezierPathWithOvalInRect: aRect]];
}

- (void) appendBezierPathWithArcWithCenter:(NSPoint)center  
									radius:(float)radius
								startAngle:(float)startAngle
								  endAngle:(float)endAngle
								 clockwise:(BOOL)clockwise
{											// startAngle and endAngle are in 
	float startAngle_rad, endAngle_rad, diff;	// degrees, counterclockwise, from 
	NSPoint p0, p1, p2, p3;						// the x axis
	
	/* 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 (_count == 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 = point1.x;
	float y1 = point1.y;
	float dx1, dy1, dx2, dy2;
	float l, a1, a2;
	NSPoint p;
	if (_count == 0)
		[self moveToPoint: point1];
	p = [self currentPoint];
	
	dx1 = p.x - x1;
	dy1 = p.y - y1;
	
	// FIXME: ??? how can this ever be negative ???
	if ((l = dx1*dx1 + dy1*dy1) <= 0)
		{
		[self lineToPoint: point1];
		return;
		}
	l = 1/sqrt(l);
	dx1 *= l;
	dy1 *= l;
	
	dx2 = point2.x - x1;
	dy2 = point2.y - y1;
	
	if ((l = dx2*dx2 + dy2*dy2) <= 0)
		{
		[self lineToPoint: point1];
		return;
		}
	
	l = 1/sqrt(l);
	dx2 *= l; 
	dy2 *= l;
	
	if ((l = dx1*dx2 + dy1*dy2) < -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;
	
	if ((l = dx1*dy2 - dx2*dy1) < 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
{
	[self appendBezierPathWithGlyphs:&glyph 
							   count:1
							  inFont:font];
}

- (void) appendBezierPathWithGlyphs:(NSGlyph *)glyphs 
							  count:(int)count
							 inFont:(NSFont *)font
{
	BACKEND;
}

- (void) appendBezierPathWithPackedGlyphs:(const char *)packedGlyphs
{
	BACKEND;
}

- (BOOL) cachesBezierPath	{ return NO; }	// no effect
- (void) setCachesBezierPath:(BOOL)flag	{ return; }	// no effect

- (void) encodeWithCoder:(NSCoder *)aCoder			// NSCoding protocol
{
	NSBezierPathElement type;
	NSPoint pts[3];
	int i, count;
	float f = [self lineWidth];
	
	[aCoder encodeValueOfObjCType: @encode(float) at: &f];
	[aCoder encodeValueOfObjCType: @encode(unsigned int) at: &_bz];
	
	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:
			default:
				break;
			}	}
}

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

	self=[self init];

	if([aCoder allowsKeyedCoding])
		{
		return NIMP;
		}

	[aCoder decodeValueOfObjCType: @encode(float) at: &f];
	[self setLineWidth: f];
	[aCoder decodeValueOfObjCType: @encode(unsigned int) at: &_bz];
	_bz.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];
			default:
				break;
			}	}
	
	return self;
}

- (id) copyWithZone:(NSZone *) zone		// NSCopying Protocol
{
	NSBezierPath *path = (NSBezierPath*)NSCopyObject (self, 0, zone);
	int i;
	if(!path)
		return nil;	// could not create copy
	if (_dashPattern != NULL)
		{
		float *pattern = objc_malloc(_dashCount * sizeof(*pattern));
		memcpy(pattern, _dashPattern, _dashCount * sizeof(*pattern));
		path->_dashPattern = pattern;
		}
	
	path->_bPath = objc_malloc(_capacity * sizeof(void *));
	for (i = 0; i < _count; i++)
		{
		PathElement *e = objc_malloc(sizeof(PathElement));
		*e = *(PathElement *)_bPath[i];
		path->_bPath[i] = e;
		}
	return path;
}

- (void) setAssociatedPoints:(NSPoint *)points atIndex:(int)index
{
	PathElement *e;
	if (index < 0 || index >= _count)
		[NSException raise:NSRangeException format:@"Bad Index"];
	e = _bPath[index];
	switch(e->type) 
		{
		case NSMoveToBezierPathElement:
		case NSLineToBezierPathElement:
			e->points[0] = points[0];
			break;
		case NSCurveToBezierPathElement:
			e->points[0] = points[0];
			e->points[1] = points[1];
			e->points[2] = points[2];
			break;
		case NSClosePathBezierPathElement:
		default:
			break;
		}
	_bz.shouldRecalculateBounds = YES;
}

- (BOOL) containsPoint:(NSPoint)point
{
	NSPoint draftPolygon[PMAX];
	int i, pcount = 0;
	double cx, cy;							// Coordinates of the current point
	double lx, ly;							// Coordinates of the last point
	int Rcross = 0;
	int Lcross = 0;	
	NSPoint p, pts[3];
	double x, y, t, k = 0.25;
	
	if(!_count || !NSPointInRect(point, [self bounds]))
		return NO;
	// FIX ME: This does not handle multiple segments!
	for(i = 0; i < _count; i++) 
		{
		NSBezierPathElement e = [self elementAtIndex: i associatedPoints: pts];
		
		if(e == NSMoveToBezierPathElement || e == NSLineToBezierPathElement) 
			{
			draftPolygon[pcount].x = pts[0].x;
			draftPolygon[pcount].y = pts[0].y;
			
			pcount++;
			} 
		else if(e == NSCurveToBezierPathElement) 
			{
			if(pcount) 
				{
				p.x = draftPolygon[pcount -1].x;
				p.y = draftPolygon[pcount -1].y;
				} 
			else 
				{
				p.x = pts[0].x;
				p.y = pts[0].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;
				
				draftPolygon[pcount].x = x;
				draftPolygon[pcount].y = y;
				pcount++;
				}
			}
		
		if (pcount == PMAX)						// Simple overflow check
			return NO;
		}  
	
	lx = draftPolygon[pcount - 1].x - point.x;
	ly = draftPolygon[pcount - 1].y - point.y;
	for(i = 0; i < pcount; i++) 
		{
		cx = draftPolygon[i].x - point.x;
		cy = draftPolygon[i].y - point.y;
		if(cx == 0 && cy == 0)							// on a vertex
			return NO;
		
		if((cy > 0)  && !(ly > 0)) 
			{
			if (((cx * ly - lx * cy) / (ly - cy)) > 0)
				Rcross++;
			}
		if((cy < 0 ) && !(ly < 0)) 
			{ 
			if (((cx * ly - lx * cy) / (ly - cy)) < 0);
			Lcross++;		
			}
		lx = cx;
		ly = cy;
		}
	
	if((Rcross % 2) != (Lcross % 2))
		return NO;										// On the border
	
	return ((Rcross % 2) == 1) ? YES : NO;
}

@end  /* NSBezierPath */


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
	*/
	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]];
		}
}
