/*	MiscUserPath.m

	Copyright 1996 Uwe Hoffmann.

	This notice may not be removed from this source code.
	The use and distribution of this software is governed by the
	terms of the MiscKit license agreement.  Refer to the license
	document included with the MiscKit distribution for the terms.

	Version 2 (August 1996)
*/

#import <Foundation/Foundation.h>
#import <AppKit/dpsOpenStep.h>
#import <AppKit/NSDPSContext.h>

#import "MiscUserPath.h"
#import "Miscuserpath.h"

@interface MiscUserPath(PrivateMethods)
 
- (void)_internalGrowParams:(unsigned int)amount;
- (void)_internalGrowOps:(unsigned int)amount;
- (void)_internalUpdateBBoxOnX:(float)x andY:(float)y;

@end

@implementation MiscUserPath
/*"The MiscUserPath class facilitates the use of Postscript user paths. It handles automatic allocation and growth of
the operator and operand arrays, as well as the calculation of the bounding box. The method #send:cached: sends the user path to the window server. A user path  may be optionally cached down at the window server, or repeatedly sent down.  
The methods #moveto::, #rmoveto::, #lineto::, #rlineto:, #curveto::::::, #rcurveto::::::, #arc:::::, #arcn:::::, #arct::::: fill the operator and operand arrays enlarging the arrays if necessary and recalculating the bounding box.
			
Because the functionality of a user path cannot be fully encapsulated in a class (for example the arrays once set cannot be modified easily to reflect a translation or rotation) the MiscUserPath class provides methods for querying and modifying all instance variables. 
The method #resetFill empties the arrays but keeps the allocated memory."*/
 

- initCountParams:(int)numParams countOps:(int)numOps
/*"Initializes a newly allocated user path object so that the operator array has the size numOps and the operand array has the size  numParams."*/
{  
   self = [super init];
   maxLengthParams = numParams;
   maxLengthOps = numOps;
   bboxParams = (float *)NSZoneMalloc([self zone],sizeof(float) * (maxLengthParams + 4));
   bbox = bboxParams;
   params = &bboxParams[4];
   bboxOps = (char *)NSZoneMalloc([self zone],maxLengthOps + 2);
   ops = &bboxOps[2];
   ping = NO;
   [self resetFill];
   return self;
}
   
- init
{  
   self = [self initCountParams:32 countOps:16];
   return self;
}

- (void)dealloc
{
   NSZoneFree([self zone], bboxParams);
   NSZoneFree([self zone], bboxOps);
   return [super dealloc];
}

- copy
{
	return [self copyWithZone:NSDefaultMallocZone()];
}

- copyWithZone:(NSZone *)zone
{
   MiscUserPath *theCopy;

   theCopy = (MiscUserPath *)NSCopyObject(self, 0, zone);
   theCopy->bboxParams = NSZoneMalloc(zone,sizeof(float) * (maxLengthParams + 4));
   theCopy->bboxOps = NSZoneMalloc(zone,maxLengthOps + 2);
   bcopy(bboxParams,theCopy->bboxParams,(lengthParams + 4) * (sizeof(float)/sizeof(char)));
   bcopy(bboxOps,theCopy->bboxOps,lengthOps + 2);
   theCopy->bbox = theCopy->bboxParams;
   theCopy->params = &(theCopy->bboxParams)[4];
   theCopy->ops = &(theCopy->bboxOps)[2]; 
   return theCopy;
}

- (NSRect)bounds
/*"Returns a NSRect with the bounding box of the user path."*/
{
   return NSMakeRect(bbox[0],bbox[1],bbox[2] - bbox[0],bbox[3] - bbox[1]);
}

- (NSPoint)currentPoint
/*"Returns the current point of the user path."*/
{
	return cp;
}

- (void)setCurrentPoint:(NSPoint)aPoint
/*"Sets the current point of the user path to aPoint."*/ 
{
	cp = aPoint;
}

- (float *)params
/*"Returns a pointer to the operand array. DonÂt reallocate the memory pointed to by this pointer. If you want to change the memory size use the methods  #doubleLengthParams, #increaseLengthParamsBy: and then ask again for the pointer with this method because the reallocating methods invalidate pointers previously returned by #params."*/
{
	return params;
}

- (float *)bbox
/*"Returns a pointer to the bounding box of the user path. The pointer points to the start of the bboxParams array. The first four float values of this array represent the bounding box of the user path. DonÂt reallocate the memory pointed to by this pointer. If you want to change the memory size use the methods  #doubleLengthParams, #increaseLengthParamsBy: and then ask again for the pointer with this method because the reallocating methods invalidate pointers previously returned by #bbox."*/
{
	return bbox;
}

- (float *)bboxParams
/*"Convenience method. Returns the same pointer as #bbox. Same restriction apply."*/
{
	return bboxParams;
}

- (char *)ops
/*"Returns a pointer to the operator array. DonÂt reallocate the memory pointed to by this pointer. If you want to change the memory size use the methods  #doubleLengthOps, #increaseLengthOpsBy: and then ask again for the pointer with this method because the reallocating methods invalidate pointers previously returned by #ops."*/
{
	return ops;
}

- (char *)bboxOps
/*"Returns a pointer to the bbox operator array. DonÂt reallocate the memory pointed to by this pointer."*/
{
	return bboxOps;
}

- (unsigned int)lengthParams
/*"Returns the amount of fill in the operand array."*/
{
	return lengthParams;
}

- (unsigned int)lengthOps
/*"Returns the amount of fill in the operator array."*/
{
	return lengthOps;
}

- (unsigned int)maxLengthParams
/*"Returns the size of the operand array."*/
{
	return maxLengthParams;
}

- (unsigned int)maxLengthOps
/*"Returns the size of the operator array."*/
{
	return maxLengthOps;
}

- (void)increaseLengthParamsBy:(unsigned int)extraLength
/*"Increases the size of the operand array by extraLength."*/
{
	[self _internalGrowParams:extraLength];
}

- (void)increaseLengthOpsBy:(unsigned int)extraLength
/*"Increases the size of the operator array by extraLength."*/
{
	[self _internalGrowOps:extraLength];
}

- (void)doubleLengthParams
/*"Doubles the size of the operand array."*/
{
	[self _internalGrowParams:lengthParams];
}

- (void)doubleLengthOps
/*"Doubles the size of the operator array."*/
{
	[self _internalGrowOps:lengthOps];
}

- (void)setMaxLengthParams:(unsigned int)length
/*"Reallocates the operand array to size length. Invalidates pointers previously returned by #bbox and #params."*/
{
	maxLengthParams = length;
	bbox = bboxParams = (float *)NSZoneRealloc([self zone], bboxParams,sizeof(float) * (maxLengthParams + 4));
	params = &bboxParams[4];
} 

- (void)setMaxLengthOps:(unsigned int)length
/*"Reallocates the operator array to size length. Invalidates pointers previously returned by #ops."*/
{
   	maxLengthOps = length;
	bboxOps = (char *)NSZoneRealloc([self zone], bboxOps,maxLengthOps + 2);
	ops = &bboxOps[2];
}

- (void)setLengthParams:(unsigned int)length
/*"Sets the amount of current operand fill to length."*/
{
	lengthParams = length;
} 

- (void)setLengthOps:(unsigned int)length
/*"Sets the amount of current operator fill to length."*/
{
   	lengthOps = length;
}

- (void)resetFill
/*"Empties the operator and operand arrays. Size of the arrays remains constant."*/
{
   lengthParams = lengthOps = 0;
   cp.x = cp.y = 0.0;
   bbox[0] = bbox[1] = 1.0e6;
   bbox[2] = bbox[3] = -1.0e6;
   bboxOps[0] = dps_ucache;
   bboxOps[1] = dps_setbbox;
}

- (void)moveto:(float)x :(float)y
/*"Adds the moveto operator (%dps_moveto) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
    if(lengthParams + 2 > maxLengthParams)
        [self _internalGrowParams:2];
    if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
    params[lengthParams++] = x;
    params[lengthParams++] = y;
    ops[lengthOps++] = dps_moveto;
    [self _internalUpdateBBoxOnX:x andY:y];
    cp.x = x;
    cp.y = y;    
}

- (void)rmoveto:(float)x :(float)y
/*"Adds the rmoveto operator (%dps_rmoveto) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
   if(lengthParams + 2 > maxLengthParams)
        [self _internalGrowParams:2];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];    
   ops[lengthOps++] = dps_rmoveto;
   params[lengthParams++] = x;
   params[lengthParams++] = y;
   cp.x += x;
   cp.y += y;
   [self _internalUpdateBBoxOnX:cp.x andY:cp.y]; 
}  

- (void)lineto:(float)x :(float)y
/*"Adds the lineto operator (%dps_lineto) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
    if(lengthParams + 2 > maxLengthParams)
        [self _internalGrowParams:2];
    if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
    ops[lengthOps++] = dps_lineto;
    params[lengthParams++] = x;
    params[lengthParams++] = y;
    [self _internalUpdateBBoxOnX:x andY:y];
    cp.x = x;
    cp.y = y;    
}

- (void)rlineto:(float)x :(float)y
/*"Adds the rlineto operator (%dps_rlineto) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/ 
{
   if(lengthParams + 2 > maxLengthParams)
        [self _internalGrowParams:2];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];    
   ops[lengthOps++] = dps_rlineto;
   params[lengthParams++] = x;
   params[lengthParams++] = y;
   cp.x += x;
   cp.y += y;
   [self _internalUpdateBBoxOnX:cp.x andY:cp.y]; 
}  

- (void)curveto:(float)x1 :(float)y1 :(float)x2 :(float)y2 :(float)x3 :(float)y3
/*"Adds the curveto operator (%dps_curveto) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
   if(lengthParams + 6 > maxLengthParams)
        [self _internalGrowParams:6];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
   ops[lengthOps++] = dps_curveto;
   params[lengthParams++] = x1;
   params[lengthParams++] = y1;
   [self _internalUpdateBBoxOnX:x1 andY:y1];
   params[lengthParams++] = x2;
   params[lengthParams++] = y2;
   [self _internalUpdateBBoxOnX:x2 andY:y2];
   params[lengthParams++] = x3;
   params[lengthParams++] = y3;
   [self _internalUpdateBBoxOnX:x3 andY:y3];
   cp.x = x3;
   cp.y = y3;    
}

- (void)rcurveto:(float)dx1 :(float)dy1 :(float)dx2 :(float)dy2 :(float)dx3 :(float)dy3
/*"Adds the rcurveto operator (%dps_rcurveto) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/ 
{
   if(lengthParams + 6 > maxLengthParams)
        [self _internalGrowParams:6];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];    
   ops[lengthOps++] = dps_rcurveto;
   params[lengthParams++] = dx1;
   params[lengthParams++] = dy1;
   params[lengthParams++] = dx2;
   params[lengthParams++] = dy2;
   params[lengthParams++] = dx3;
   params[lengthParams++] = dy3;
   [self _internalUpdateBBoxOnX:cp.x + dx1 andY:cp.y + dx1];
   [self _internalUpdateBBoxOnX:cp.x + dx2 andY:cp.y + dx2];
   [self _internalUpdateBBoxOnX:cp.x + dx3 andY:cp.y + dx3];
   cp.x += dx3;
   cp.y += dy3; 
}  

- (void)arc:(float)x :(float)y :(float)r :(float)ang1 :(float)ang2
/*"Adds the arc operator (%dps_arc) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
   if(lengthParams + 5 > maxLengthParams)
        [self _internalGrowParams:5];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
   ops[lengthOps] = dps_arc;
   lengthOps = lengthOps + 1;
   params[lengthParams++] = x;
   params[lengthParams++] = y;
   params[lengthParams++] = r;
   params[lengthParams++] = ang1;
   params[lengthParams++] = ang2;
   [self _internalUpdateBBoxOnX:x + r andY:y + r];
   [self _internalUpdateBBoxOnX:x - r andY:y - r];   
   cp.x = x + cos(ang2 / 57.3) * r;
   cp.y = y + sin(ang2 / 57.3) * r;    
}

- (void)arcn:(float)x :(float)y :(float)r :(float)ang1 :(float)ang2
/*"Adds the arcn operator (%dps_arcn) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
   if(lengthParams + 5 > maxLengthParams)
        [self _internalGrowParams:5];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
   ops[lengthOps++] = dps_arcn;
   params[lengthParams++] = x;
   params[lengthParams++] = y;
   params[lengthParams++] = r;
   params[lengthParams++] = ang1;
   params[lengthParams++] = ang2;
   [self _internalUpdateBBoxOnX:x + r andY:y + r];
   [self _internalUpdateBBoxOnX:x - r andY:y - r];   
   cp.x = x + cos(ang2 / 57.3) * r;
   cp.y = y + sin(ang2 / 57.3) * r;    
}

- (void)arct:(float)x1 :(float)y1 :(float)x2 :(float)y2 :(float)r
/*"Adds the arct operator (%dps_arct) to the operator array and the arguments to the operand array. Enlarges arrays if necessary by doubling the current size. Recalculates the bounding box of the user path."*/
{
   if(lengthParams + 5 > maxLengthParams)
        [self _internalGrowParams:5];
   if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
   ops[lengthOps++] = dps_arct;
   params[lengthParams++] = x1;
   params[lengthParams++] = y1;
   params[lengthParams++] = x2;
   params[lengthParams++] = y2;
   params[lengthParams++] = r;
   [self _internalUpdateBBoxOnX:x1 andY:y1];
   [self _internalUpdateBBoxOnX:x2 andY:y2];   
   cp.x = x2;
   cp.y = y2;    
}

- (void)closepath
/*"Adds the closepath operator (%dps_closepath) to the operator array."*/ 
{
	if(lengthOps + 1 > maxLengthOps)
        [self _internalGrowOps:1];
   	ops[lengthOps++] = dps_closepath; 
}


//************************************************************************
// sending 

- (void)debugSend:(int)op cached:(BOOL)cache
/*"Sends the user path with the rendering operation op to the window server and calls an NSPing() afterwards. If cached is YES the user path will be cached at the window server.  op should be one of the following values: %{dps_ustroke, dps_ufill, dps_ueofill, dps_ustrokepath, dps_uappend, dps_inufill, dps_inueofill, dps_inustroke}."*/
{
   ping = YES;
   return [self send:op cached:cache];
}

- (void)send:(int)op cached:(BOOL)cache
/*"Sends the user path with the rendering operation op to the window server and calls an NSPing() afterwards. If cached is YES the user path will be cached at the window server.  op should be one of the following values: %{dps_ustroke, dps_ufill, dps_ueofill, dps_ustrokepath, dps_uappend, dps_inufill, dps_inueofill, dps_inustroke}."*/
{
	if(cache)
            DPSDoUserPath([[NSDPSContext currentContext] DPSContext],params, lengthParams, dps_float, bboxOps,
		      lengthOps + 2, bbox, op);
	else
	    DPSDoUserPath([[NSDPSContext currentContext] DPSContext],params, lengthParams, dps_float, bboxOps + 1,
		      lengthOps + 1, bbox, op);
	if(ping)
	    PSWait();	
}

//************************************************************************
// NSCoding

- (void)encodeWithCoder:(NSCoder *)coder
{
   	[coder encodeValuesOfObjCTypes:"iiii{ff}",&lengthParams,&lengthOps,&maxLengthParams,&maxLengthOps,&cp];
   	[coder encodeArrayOfObjCType:"f" count:lengthParams + 4 at:bboxParams];
   	[coder encodeArrayOfObjCType:"c" count:lengthOps + 2 at:bboxOps];
}

- initWithCoder:(NSCoder *)coder
{
   	[coder decodeValuesOfObjCTypes:"iiii{ff}",&lengthParams,&lengthOps,&maxLengthParams,&maxLengthOps,&cp];
   	bboxParams = NSZoneMalloc([self zone],sizeof(float) * (maxLengthParams + 4));
   	bboxOps = NSZoneMalloc([self zone],maxLengthOps + 2);
	[coder decodeArrayOfObjCType:"f" count:lengthParams + 4 at:bboxParams];
   	[coder decodeArrayOfObjCType:"c" count:lengthOps + 2 at:bboxOps];
   	ping = NO;
   	bbox = bboxParams;
   	params = &bboxParams[4];
   	ops = &bboxOps[2];
   	return self;
}
              
@end

@implementation MiscUserPath(PrivateMethods)

- (void)_internalGrowParams:(unsigned int)amount
{
	unsigned oldMax;
	
	oldMax = maxLengthParams;
   	maxLengthParams *= 2;
	while(maxLengthParams < oldMax + amount)
		maxLengthParams *= 2;
	bbox = bboxParams = (float *)NSZoneRealloc([self zone], bboxParams,sizeof(float) * (maxLengthParams + 4));
	params = &bboxParams[4]; 
}

- (void)_internalGrowOps:(unsigned int)amount
{
	unsigned oldMax;
	
	oldMax = maxLengthOps;
   	maxLengthOps *= 2;
	while(maxLengthOps < oldMax + amount)
		maxLengthOps *= 2;
	bboxOps = (char *)NSZoneRealloc([self zone], bboxOps,maxLengthOps + 2);
	ops = &bboxOps[2];
}

- (void)_internalUpdateBBoxOnX:(float)x andY:(float)y
{
   if(x < bbox[0])
	bbox[0] = x;
   if(y < bbox[1])
	bbox[1] = y;
   if(x > bbox[2])
	bbox[2] = x;
   if(y > bbox[3])
	bbox[3] = y;
}

@end
