/* 
 * UserPath.m by Bruce Blumberg, NeXT Computer, Inc.
 *
 * You may freely copy,distribute and re-use the code in this example. NeXT
 * disclaims any warranty of any kind, expressed or implied, as to its fitness
 * for any particular purpose
 *
 */

#import "UserPath.h"
#import <mach/mach_init.h>
#import <appkit/graphics.h>
#import <appkit/errors.h>
#import <math.h>
#import <libc.h>

static NXZone      *upZone = NULL;

NXZone *userPathZone()
{
    if (!upZone)
		upZone = NXCreateZone(vm_page_size, vm_page_size, 1);
    
    return upZone;
}

@implementation UserPath

- init
{
	[super init];
    max = 32;
    points = (float *)NXZoneMalloc(userPathZone(), sizeof(float) * max);
    ops = (char *)NXZoneMalloc(userPathZone(), (2 + (max / 2)) * sizeof(char));
    ping = NO;
    
    return self;
}

- free
{
    free(points);
    free(ops);
    return [super free];
}

- grow
{
    max *= 2;
    points = (float *)NXZoneRealloc(userPathZone(), points, sizeof(float) * max);
    ops = (char *)NXZoneRealloc(userPathZone(), ops, (2 + (max / 2)) * sizeof(char));
    return self;
}

- begin:(BOOL)cache
{
    numberOfPoints = numberOfOps = 0;
    cp.x = cp.y = 0;
    bbox[0] = bbox[1] = 1.0e6;
    bbox[2] = bbox[3] = -1.0e6;
	
    if (cache)
		ops[numberOfOps++] = dps_ucache;
		
    ops[numberOfOps++] = dps_setbbox;
    opForUserPath = 0;
    
    return self;
}

- end:(int)op
{
    opForUserPath = op;
    return self;
}


- debug:(BOOL)shouldPing
{
    ping = shouldPing;
    return;
}

- (int)send
/*
 * Call this to send the path down to the server. If ping==YES (set via
 * debug:), the function will send an NXPing() after the Path. In any event,
 * code is bracketed by a NX_DURING ... NX_HANDLER construct which will try to
 * catch postscript errors.  If ping==NO (the default) it is unlikely to catch
 * errors, with ping==YES it will. Whether you can recover or not is another
 * matter. sendUserPath returns 0 on success and -1 on failure. If no previous
 * endUserPath: has been sent, will return -2 and will not send the path to the
 * server.
 */
{
    NXHandler           exception;

    exception.code = 0;
    if (opForUserPath != 0) 
	{
		NX_DURING
		DPSDoUserPath(	points, numberOfPoints, dps_float, ops, 
						numberOfOps, bbox, opForUserPath);
		if (ping)
	 	   NXPing();
	
      	NX_HANDLER
		exception = NXLocalHandler;
      	NX_ENDHANDLER
	
		if (exception.code) 
		{
	    	NXReportError(&exception);
	    	if (exception.code == dps_err_ps) 
			{
				return -1;
	    	}
		} 
		else 
		{
	    	return 0;
		}
    }
    return -1;
}

- checkBBox:(float)x :(float)y
/* Checks if bounding box needs to be enlarged based on x and 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;
    
    return self;
}

- addPts:(NXPoint *)p
/* adds x and y to user path. Updates bounding box as necessary */
{
    if (!((numberOfPoints + 2) < max)) 
		[self grow];
    
    points[numberOfPoints++] = p->x;
    points[numberOfPoints++] = p->y;
	[self checkBBox:p->x :p->y];
    return self;
}

- addOp:(int)op
/*
 * adds operator to user path.  Operator should be one of the following:
 * 	dps_moveto, dps_rmoveto, dps_lineto, dps_rlineto, dps_curveto,
 *	dps_rcurveto, dps_arc, dps_arcn, dps_arct, dps_closepath.
 */
{
    ops[numberOfOps++] = op;    
    return self;
}

- addOp:(int)op  point:(NXPoint *)p
/*
 * adds operator and x and y to user path. Operator should be one of the
 * operators above
 */
{
    if (!((numberOfPoints + 2) < max))
		[self grow];
    
    ops[numberOfOps++] = op;
    points[numberOfPoints++] = p->x;
    points[numberOfPoints++] = p->y;
	[self checkBBox:p->x :p->y];
    return self;
}

- addOp:(int)op :(float)x :(float)y
/*
 * adds operator and x and y to user path. Operator should be one of the
 * operators above
 */
{
    if (!((numberOfPoints + 2) < max))
		[self grow];
    
    ops[numberOfOps++] = op;
    points[numberOfPoints++] = x;
    points[numberOfPoints++] = y;
	[self checkBBox:x :y];
    return self;
}

- moveTo:(float)x :(float)y
/* 
 * adds <x y moveto> to user path and updates bounding box 
 */
{
    [self addOp: dps_moveto :x :y];
    cp.x = x;
    cp.y = y;
    return self;
}


- moveTo:(NXPoint *)p
/* 
 * adds <x y moveto> to user path and updates bounding box 
 */
{
    [self addOp: dps_moveto point:p];
    cp.x = p->x;
    cp.y = p->y;
    return self;
}

- rmoveTo:(float)x :(float)y
/* 
 * adds <x y rmoveto> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 2) < max))
		[self grow];

    ops[numberOfOps++] = dps_rmoveto;
    points[numberOfPoints++] = x;
    points[numberOfPoints++] = y;
    cp.x += x;
    cp.y += y;
	[self checkBBox:x :y];
    
    return self;
}

- rmoveTo:(NXPoint *)p
/* 
 * adds <x y rmoveto> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 2) < max))
		[self grow];

    ops[numberOfOps++] = dps_rmoveto;
    points[numberOfPoints++] = p->x;
    points[numberOfPoints++] = p->y;
    cp.x += p->x;
    cp.y += p->y;
	[self checkBBox:p->x :p->y];
    
    return self;
}

- lineTo:(float)x :(float)y
/* 
 * adds <x y lineto> to user path and updates bounding box 
 */
{
    [self addOp:dps_lineto :x :y];
    cp.x = x;
    cp.y = y;
    return self;
}

- lineTo:(NXPoint *)p
/* 
 * adds <x y lineto> to user path and updates bounding box 
 */
{
    [self addOp:dps_lineto point:p];
    cp.x = p->x;
    cp.y = p->y;
    return self;
}

- rlineTo:(float)x :(float)y
/* 
 * adds <x y rlineto> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 2) < max))
		[self grow];

    ops[numberOfOps++] = dps_rlineto;
    points[numberOfPoints++] = x;
    points[numberOfPoints++] = y;
    cp.x += x;
    cp.y += y;
	[self checkBBox:x :y];
    
    return;
}

- rlineTo:(NXPoint *)p
/* 
 * adds <x y rlineto> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 2) < max))
		[self grow];

    ops[numberOfOps++] = dps_rlineto;
    points[numberOfPoints++] = p->x;
    points[numberOfPoints++] = p->y;
    cp.x += p->x;
    cp.y += p->y;
	[self checkBBox:p->x :p->y];
    
    return self;
}

- curveTo:(float)x1 :(float)y1 :(float)x2 :(float)y2 :(float)x3 :(float)y3
/* 
 * adds <x1 y1 x2 y2 curveto> to user path and updates bounding box 
 */
{
	[self addPts:x1 :y1];
	[self addPts:x2 :y2];
	[self addOp:dps_curveto :x3 :y3];
    cp.x = x3;
    cp.y = y3;
    
    return self;
}

- rcurveTo:(float)x1 :(float)y1 :(float)x2 :(float)y2 :(float)x3 :(float)y3
/* 
 * adds <x1 y1 x2 y2 rcurveto> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 6) < max))
		[self grow];

    ops[numberOfOps++] = dps_rcurveto;
    points[numberOfPoints++] = x1;
    points[numberOfPoints++] = y1;
    points[numberOfPoints++] = x2;
    points[numberOfPoints++] = y2;
    points[numberOfPoints++] = x3;
    points[numberOfPoints++] = y3;
	[self checkBBox:(cp.x + x1) :(cp.y + y1)];
	[self checkBBox:(cp.x + x2) :(cp.y + y2)];
	[self checkBBox:(cp.x + x3) :(cp.y + y3)];
    cp.x = x3;
    cp.y = y3;

    return self;
}

- arc:(float)x :(float)y :(float)r :(float)ang1 :(float)ang2
/* 
 * adds <x y r ang1 ang2 arc> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 5) < max))
		[self grow];

    ops[numberOfOps++] = dps_arc;
    points[numberOfPoints++] = x;
    points[numberOfPoints++] = y;
    points[numberOfPoints++] = r;
    points[numberOfPoints++] = ang1;
    points[numberOfPoints++] = ang2;
	[self checkBBox:(x + r) :(y + r)];
	[self checkBBox:(x - r) :(y - r)];
    cp.x = x + cos(ang2 / 57.3) * r;
    cp.y = y + sin(ang2 / 57.3) * r;
    
    return self;
}

- arcn:(float)x :(float)y :(float)r :(float)ang1 :(float)ang2
/* 
 * adds <x y r ang1 ang2 arcn> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 5) < max))
		[self grow];

    ops[numberOfOps++] = dps_arcn;
    points[numberOfPoints++] = x;
    points[numberOfPoints++] = y;
    points[numberOfPoints++] = r;
    points[numberOfPoints++] = ang1;
    points[numberOfPoints++] = ang2;
	[self checkBBox:(x + r) :(y + r)];
	[self checkBBox:(x - r) :(y - r)];
    cp.x = x + cos(ang2 / 57.3) * r;
    cp.y = y + sin(ang2 / 57.3) * r;
    
    return self;
}

- arct:(float)x1 :(float)y1 :(float)x2 :(float)y2 :(float)r
/* 
 * adds <x1 y1 x2 y2 r arct> to user path and updates bounding box 
 */
{
    if (!((numberOfPoints + 5) < max)) 
		[self grow];

    ops[numberOfOps++] = dps_arcn;
    points[numberOfPoints++] = x1;
    points[numberOfPoints++] = y1;
    points[numberOfPoints++] = x2;
    points[numberOfPoints++] = y2;
    points[numberOfPoints++] = r;
	[self checkBBox:x1 :y1];
	[self checkBBox:x2 :y2];
    cp.x = x2;
    cp.y = y2;
    
    return self;
}

- close
/* 
 * adds <closepath> to user path and updates bounding box 
 */
{
    ops[numberOfOps++] = dps_closepath;
    return self;
}

@end
