/*
Pencil V1.0, Copyright 1994, 95 by Florian Marquardt.
This program may be distributed under the terms of the GNU general
public license (Start Pencil and select "Info>COPYING..." for a copy of the
license).
*/
#import "PencilGraphic.h"
#import "PencilView.h"
#import <math.h>

#define ADJUST [view convertPoint:&te->location fromView:nil]
#define EX te->location.x
#define EY te->location.y

#define M_180_PI 180.0/M_PI

extern float athresh, sumthresh;
extern float *fpts;
extern int maxfpts;

extern NXStream *result;

extern double fabs(double x);

BOOL globalOutlines=NO;

float angle(float x1,float y1,float x2,float y2)
{
	return atan2(-y1*x2+x1*y2,x1*x2+y1*y2);
}

void expandRect (NXRect *re, float x, float y)
{
	if(x<re->origin.x)	{ re->size.width+=re->origin.x-x; re->origin.x=x; }
	if(x>re->origin.x+re->size.width)	{ re->size.width=x-re->origin.x; }
	if(y<re->origin.y)	{ re->size.height+=re->origin.y-y; re->origin.y=y; }
	if(y>re->origin.y+re->size.height)	{ re->size.height=y-re->origin.y; }
}

#define TRANSFORMX(a,b) (tx+sx*cos(phi)*(a)-sy*sin(phi)*(b))
#define TRANSFORMY(a,b) (ty+sx*sin(phi)*(a)+sy*cos(phi)*(b))


void read_brace_string(NXStream *stream, char *to)
{
	int open=0;
	char a;
	int shouldLoop=1;
	
	*to=0;
	
	NXScanf(stream, " %*[^{]{");
	do {
		NXScanf(stream, "%[^{}]", to);
		to+=strlen(to);
		if((a=NXGetc(stream))=='{') {
			++open; *(to++)='{';
		} else {
			if(a=='}') {
				--open;
				if(open<0) shouldLoop=0; else *(to++)='}';
			}
			else
				if(a<=0)	shouldLoop=0;
		}
	} while(shouldLoop);
}

@implementation PencilGraphic
+ initialize
{
	[self setVersion:1];
	return self;
}

- (void)transformPoint:(NXPoint *)pt
{
	float xh, yh;

	if(transformed)
	{
	xh=pt->x-tx;
	yh=pt->y-ty;
	pt->x=(xh*cos(phi)+yh*sin(phi))/sx;
	pt->y=(-xh*sin(phi)+yh*cos(phi))/sy;
	}
}

- (void)rotateAroundCenter:(float)x:(float)y fromPoint:(NXPoint *)pt1 toPoint:(NXPoint *)pt2
{
	float dphi, dx, dy;

	dphi=angle(pt1->x-x,pt1->y-y,pt2->x-x,pt2->y-y);
	phi+=dphi;
	dx=tx-x;
	dy=ty-y;
	tx=x+cos(dphi)*dx-sin(dphi)*dy;
	ty=y+sin(dphi)*dx+cos(dphi)*dy;
	if(fabs(phi)<=1e-6) { phi=0; rotated=NO; } else { rotated=YES; }
	if(hypot(tx,ty)<=1e-6) { tx=ty=0; translated=NO; } else { translated=YES; }
	transformed=(translated || scaled || rotated) ? YES:NO;
}

- (void)scaleCenter:(float)cx:(float)cy by:(float)scx:(float)scy
{
	NXPoint c;
	
	c.x=cx;
	c.y=cy;
	[self transformPoint:&c];
	tx=TRANSFORMX(c.x*(1-scx),c.y*(1-scy));
	ty=TRANSFORMY(c.x*(1-scx),c.y*(1-scy));
	sx*=scx;
	sy*=scy;
	if(fabs(sx-1.0)+fabs(sy-1.0)<1e-6) { sx=sy=1; scaled=NO; } else { scaled=YES; }
	if(hypot(tx,ty)<=1e-6) { tx=ty=0; translated=NO; } else { translated=YES; }
	transformed=(translated || scaled || rotated) ? YES:NO;
}

- (BOOL)selected:(NXEvent *)te:(int *)cp:(id)view
{
	int val;
	NXPoint pt;

	pt=te->location;
	[view lockFocus];
	[self transformPoint:&pt];
	if(data)	DPSPrintf(DPSGetCurrentContext()," /udt { %s } def ", data); else DPSPrintf(DPSGetCurrentContext()," /udt {} def ");
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def /nctrl %d def /mmx %g def /mmy %g def /cp { %s } def /linw %g def %sSel\n", ncontrolpts, pt.x, pt.y, methodname, linewidth, drawingmethod);
	PSgetboolean(&val);
	[view unlockFocus];
	return val;
}

- (BOOL)move:(NXEvent *)tte:(int *)cp:(id)view:(float)bsize
{
    	int	oldMask;
	BOOL shouldLoop=YES;
	int val;
	NXEvent *te;
	NXEvent mte;

	mte=*tte;
	te=&mte;
	[view lockFocus];
	[self transformPoint:&te->location];
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def /nctrl %d def /mmx %g def /mmy %g def genselpt\n", ncontrolpts, EX, EY);
	PSgetint(&val);
	
	if(val>=0)
	{
		float dx, dy;
		NXEvent thisEvent;
		
		*cp=val;
    	oldMask = [[view window] addToEventMask:NX_LMOUSEDRAGGEDMASK];

	PSsetinstance(YES);
	dx=controlpts[2**cp]-EX;
	dy=controlpts[2**cp+1]-EY;
    while (shouldLoop) {
        do {
	te = [NXApp getNextEvent:(NX_LMOUSEUPMASK |
                                         NX_LMOUSEDRAGGEDMASK)];
    	} while(te->type==NX_LMOUSEDRAGGED && ([NXApp peekNextEvent:NX_LMOUSEDRAGGEDMASK into:&thisEvent]));

   	ADJUST;
	if(te->type==NX_LMOUSEUP)
		shouldLoop=NO; 
	else {
	[self transformPoint:&te->location];
	controlpts[2**cp]=EX+dx;
	controlpts[2**cp+1]=EY+dy;
	PSnewinstance();
	[self drawControl:NULL:*cp:bsize];
	PSflushgraphics();
	NXPing();
	}
    }
	PSsetinstance(NO);
	[view unlockFocus];
    [[view window] setEventMask:oldMask];
		
		return YES;
	}
	else
	{
		[view unlockFocus];
		return NO;
	}
}

- create:(NXEvent *)tte:(int *)cp:(id)view:(float)bsize;
{
    int            oldMask;
	BOOL shouldLoop=YES;
	NXEvent *te;
	NXEvent mte;
	NXEvent thisEvent;
	
	mte=*tte;
	te=&mte;

    oldMask = [[view window] addToEventMask:NX_LMOUSEDRAGGEDMASK | NX_RMOUSEDOWNMASK];

	ADJUST;
	controlpts=(float *)malloc(sizeof(float)*2);
	controlpts[0]=EX; controlpts[1]=EY;
	ncontrolpts=1;
	[view lockFocus];
	PSsetinstance(YES);
	[self drawControl:NULL:0:bsize];
	PSflushgraphics();
    while (shouldLoop) {
            do {
        te = [NXApp getNextEvent:(NX_LMOUSEUPMASK |
                                         NX_LMOUSEDRAGGEDMASK | NX_RMOUSEDOWNMASK | NX_LMOUSEDOWNMASK)];
    	} while(te->type==NX_LMOUSEDRAGGED && ([NXApp peekNextEvent:NX_LMOUSEDRAGGEDMASK into:&thisEvent]));


        switch (te->type) {
        case NX_LMOUSEUP:
	break;
	case NX_LMOUSEDRAGGED:
	ADJUST;
	controlpts[2*(ncontrolpts-1)]=EX;
	controlpts[2*(ncontrolpts-1)+1]=EY;
	PSnewinstance();
	[self drawControl:NULL:ncontrolpts-1:bsize];
	PSflushgraphics();
	NXPing();
             break;
        case NX_LMOUSEDOWN:
	ADJUST;
	ncontrolpts++;
	controlpts=(float *)realloc(controlpts, sizeof(float)*2*ncontrolpts);
	controlpts[2*(ncontrolpts-1)]=EX;
	controlpts[2*(ncontrolpts-1)+1]=EY;
	PSnewinstance();
	[self drawControl:NULL:ncontrolpts-1:bsize];
	PSflushgraphics();
	NXPing();
            break;
	case NX_RMOUSEDOWN:
	shouldLoop=NO;
	*cp=ncontrolpts-1;
	break;
        default:
            break;
        }

    }
	PSsetinstance(NO);
	[view unlockFocus];
    [[view window] setEventMask:oldMask];
	return self;
}

#define PX(i) fpts[(i)*2]
#define PY(i) fpts[(i)*2+1]
#define NLINMAX 1000

- createPolyFreehand:(NXEvent *)tte:(int *)cp:(id)view:(float)bsize
{
    int            oldMask;
	BOOL shouldLoop=YES;
	NXEvent *te;
	NXEvent mte;
	int n=1, nlin=0, i0=0, i, j;
	float ax, ay, ttx, tty, tl, sum, amax, dx, dy, val;

	mte=*tte;
	te=&mte;

    oldMask = [[view window] addToEventMask:NX_LMOUSEDRAGGEDMASK];

	ADJUST;

	controlpts=(float *)malloc(sizeof(float)*2);
	controlpts[0]=EX; controlpts[1]=EY;
	ncontrolpts=1;
	[view lockFocus];
	PSnewinstance();
	PSsetinstance(YES);
	PSsetgray(0);
	PSrectfill(EX,EY,1,1);
	PSflushgraphics();
	PX(0)=EX; PY(0)=EY;
    while (shouldLoop) {
        te = [NXApp getNextEvent:(NX_LMOUSEUPMASK |
                                         NX_LMOUSEDRAGGEDMASK)];

	ADJUST;
	PSrectfill(EX,EY,1,1);
	PSflushgraphics();
	if(n>=maxfpts)	{ fpts=(float *)realloc(fpts, sizeof(float)*2*(maxfpts*=2)); }
	PX(n)=EX; PY(n)=EY; ++n;
	if(te->type==NX_LMOUSEUP)
		shouldLoop=NO;
        }
	PSsetinstance(NO);
	[view unlockFocus];
    [[view window] setEventMask:oldMask];

	controlpts=(float *)malloc(sizeof(float)*2);
	controlpts[0]=PX(0); controlpts[1]=PY(0);
	ncontrolpts=1;

	--n;

	while(++nlin<=NLINMAX && i0<n-1)
	{
		ax=PX(i0);
		ay=PY(i0);

		for(i=i0+2;i<=n;i++)	{
		ttx=PX(i)-ax;
		tty=PY(i)-ay;
		tl=hypot(ttx,tty);
		ttx/=tl;
		tty/=tl;
		sum=0;
		amax=0;
		for(j=i0+1;j<=i-1;j++)	{
		dx=PX(j)-ax;
		dy=PY(j)-ay;
		val=ttx*dy-tty*dx;
		sum+=val;
		val=fabs(val);
		if(val>amax)	amax=val;
		}
		if(amax>athresh && fabs(sum/(i-i0-1))>sumthresh) break;
		}
		if(i0<i-1) i0=i-1; else i0=i;
		if(i0>n)	i0=n;
		ncontrolpts++;
		controlpts=(float *)realloc(controlpts, sizeof(float)*2*ncontrolpts);
		controlpts[2*(ncontrolpts-1)]=PX(i0);
		controlpts[2*ncontrolpts-1]=PY(i0);
	}
	[view lockFocus];
	PSsetinstance(YES);
	PSnewinstance();
	[self drawControl:NULL:ncontrolpts-1:bsize];
	PSsetinstance(NO);
	PSflushgraphics();
	[view unlockFocus];
	*cp=ncontrolpts-1;
	return self;
}

- (void)drawPath
{
	DPSPrintf(DPSGetCurrentContext()," matrix currentmatrix ");
	if(translated)	PStranslate(tx,ty);
	if(rotated)		PSrotate(M_180_PI*phi);
	if(scaled)		PSscale(sx,sy);
	if(data)	DPSPrintf(DPSGetCurrentContext()," /udt { %s } def ", data); else DPSPrintf(DPSGetCurrentContext()," /udt {} def ");
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def /nctrl %d def %s setmatrix\n", ncontrolpts, methodname);
}

- (void)draw:(NXRect *)re
{
	if(!re || NXIntersectsRect(&bounds, re))
	{
	if(globalOutlines)
	{
	PSgsave();
	if(translated)	PStranslate(tx,ty);
	if(rotated)		PSrotate(M_180_PI*phi);
	if(scaled)		PSscale(sx,sy);
	if(data)	DPSPrintf(DPSGetCurrentContext()," /udt { %s } def ", data); else DPSPrintf(DPSGetCurrentContext()," /udt {} def ");
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def 0 setlinewidth 0 setgray /nctrl %d def %s stroke\n", ncontrolpts, methodname);
	PSgrestore();
	}
	else
	{
	if(transformed)
	{
	PSgsave();
	if(translated)	PStranslate(tx,ty);
	if(rotated)		PSrotate(M_180_PI*phi);
	if(scaled)		PSscale(sx,sy);
	if(data)	DPSPrintf(DPSGetCurrentContext()," /udt { %s } def ", data); else DPSPrintf(DPSGetCurrentContext()," /udt {} def ");
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def /cl { %g %g %g %g %g %g } def %g setlinewidth /nctrl %d def /cfl { %s } def /cst { %s } def /cp { %s } def %s\n", c1[0], c1[1], c1[2], c2[0], c2[1], c2[2], linewidth, ncontrolpts, fillmethod, strokemethod, methodname, drawingmethod);
	PSgrestore();
	}
	else
	{
	if(data)	DPSPrintf(DPSGetCurrentContext()," /udt { %s } def ", data); else DPSPrintf(DPSGetCurrentContext()," /udt {} def ");
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def /cl { %g %g %g %g %g %g } def %g setlinewidth /nctrl %d def /cfl { %s } def /cst { %s } def /cp { %s } def %s\n", c1[0], c1[1], c1[2], c2[0], c2[1], c2[2], linewidth, ncontrolpts, fillmethod, strokemethod, methodname, drawingmethod);
	}
	}
	}
}

- calculateBoundingBox:(id)view
{
	float llx, lly, urx, ury;

	if(view) [view lockFocus];
	PSgsave();
	if(data)	DPSPrintf(DPSGetCurrentContext()," /udt { %s } def ", data); else DPSPrintf(DPSGetCurrentContext()," /udt {} def ");
	DPSPrintf(DPSGetCurrentContext(), " /ctrl ");
	if(controlpts) PSsendfloatarray(controlpts, 2*ncontrolpts); else DPSPrintf(DPSGetCurrentContext(), "[ ]");
	DPSPrintf(DPSGetCurrentContext()," def /cl { %g %g %g %g %g %g } def /linw %g def /nctrl %d def /cfl { %s } def /cst { %s } def /cp { %s } def %sBB\n", c1[0], c1[1], c1[2], c2[0], c2[1], c2[2], linewidth, ncontrolpts, fillmethod, strokemethod, methodname, drawingmethod);
	PSgetfloat(&ury);
	PSgetfloat(&urx);
	PSgetfloat(&lly);
	PSgetfloat(&llx);
	PSgrestore();
	if(view) [view unlockFocus];
	bounds.origin.x=TRANSFORMX(llx,lly);
	bounds.origin.y=TRANSFORMY(llx,lly);
	bounds.size.width=bounds.size.height=0;
	expandRect(&bounds, TRANSFORMX(llx,ury),TRANSFORMY(llx,ury));
	expandRect(&bounds, TRANSFORMX(urx,ury), TRANSFORMY(urx,ury));
	expandRect(&bounds, TRANSFORMX(urx,lly),TRANSFORMY(urx,lly));
	return self;
}

- giveBounds:(NXRect *)bnd
{
	*bnd=bounds;
	return self;
}

- (void)drawControl:(NXRect *)re:(int)cp:(float)bsize
{
	[self draw:re];
	if(transformed)	{	PSgsave();
	if(translated)	PStranslate(tx,ty);
	if(rotated)		PSrotate(M_180_PI*phi);
	if(scaled)		PSscale(sx,sy);
	}
	bsize/=sx;
	DPSPrintf(DPSGetCurrentContext()," /bsz %g def /bszh %g def /cctrl %d def %scontrol\n", bsize, bsize/2, cp, methodname);
	if(transformed)	PSgrestore();
}

- initWithSettings:(char *)name:(NXColor)co1:(NXColor)co2:(float)lw:(char *)dm:(char *)fm:(char *)sm:(char *)ud
{
	[self setMethodname:name];
	[self setColor1:co1];
	[self setColor2:co2];
	[self setLineWidth:lw];
	[self setDrawingMethod:dm];
	[self setFillMethod:fm];
	[self setStrokeMethod:sm];
	[self setSpecialAttributes:ud];
	ncontrolpts=0;
	sx=sy=1;
	transformed=NO;
	return self;
}

// to be called for convertRTFtoCharPath:
- initWithControlPt: (float)x:(float)y
{
	ncontrolpts=1;
	if(controlpts) free(controlpts);
	controlpts=(float *)malloc(sizeof(float)*2);
	controlpts[0]=x;
	controlpts[1]=y;
	sx=sy=1;
	transformed=NO;
	return self;
}

- giveSettings:(char **)name:(NXColor *)co1:(NXColor *)co2:(float *)lw:(char **)dmeth:(char **)fillmeth:(char **)strokemeth:(char **)ud
{
	*name=methodname;
	*co1=NXConvertRGBToColor(c1[0],c1[1],c1[2]);
	*co2=NXConvertRGBToColor(c2[0],c2[1],c2[2]);
	*lw=linewidth;
	*dmeth=drawingmethod; *strokemeth=strokemethod; *fillmeth=fillmethod; *ud=data; return self;
}
- setMethodname:(char*)name { if(methodname) free(methodname); methodname=(char *)malloc(sizeof(char)*(strlen(name)+1)); strcpy(methodname, name); return self; }
- setDrawingMethod:(char *)name { if(drawingmethod) free(drawingmethod); drawingmethod=(char *)malloc(sizeof(char)*(strlen(name)+1)); strcpy(drawingmethod, name); return self; }
- setStrokeMethod:(char *)name  { if(strokemethod) free(strokemethod); strokemethod=(char *)malloc(sizeof(char)*(strlen(name)+1)); strcpy(strokemethod, name); return self; }
- setSpecialAttributes:(char *)name  { if(data) free(data); data=(char *)malloc(sizeof(char)*(strlen(name)+1)); strcpy(data, name); return self; }
- setFillMethod:(char *)name  { if(fillmethod) free(fillmethod); fillmethod=(char *)malloc(sizeof(char)*(strlen(name)+1)); strcpy(fillmethod, name); return self; }

- setColor1:(NXColor)col { NXConvertColorToRGB(col, &c1[0], &c1[1], &c1[2]); return self; }
- setColor2:(NXColor)col { NXConvertColorToRGB(col, &c2[0], &c2[1], &c2[2]); return self; }
- setLineWidth:(float)lw { linewidth=lw; return self; }
- addTranslation:(float)dtx:(float)dty
{
	tx+=dtx;	ty+=dty;
	if(pow(tx,2)+pow(ty,2)<=1e-6)	{	tx=ty=0; translated=NO; }
		else translated=YES;
	transformed=(translated || scaled || rotated) ? YES:NO;
	return self;
}

- centerAt:(NXPoint *)c
{
	return [self addTranslation:(c->x-bounds.origin.x-bounds.size.width/2):(c->y-bounds.origin.y-bounds.size.height/2)];
}

- free
{
	if(controlpts) free(controlpts);
	return [super free];
}

#define X(a) controlpts[(a)*2]
#define Y(a) controlpts[(a)*2+1]

- insertNextPoint:(int *)cp
{
	if(controlpts)
	{
		if(*cp>=0 && *cp<ncontrolpts)
		{
			int i;

			controlpts=(float *)realloc(controlpts, sizeof(float)*(ncontrolpts+=2)*2);
			for(i=ncontrolpts-1;i>*cp+1;i--) { X(i)=X(i-2);  Y(i)=Y(i-2); }
			*cp+=2;
			if(*cp<ncontrolpts-3)
			{	X(*cp)=(X(*cp-2)+X(*cp+2))/2; Y(*cp)=(Y(*cp+2)+Y(*cp-2))/2;
				X(*cp+1)=(X(*cp-1)+X(*cp+3))/2; Y(*cp+1)=(Y(*cp-1)+Y(*cp+3))/2; }
			else
			{
X(*cp)=(X(*cp-2)+X(0))/2; Y(*cp)=(Y(0)+Y(*cp-2))/2;
				X(*cp+1)=(X(*cp-1)+X(1))/2; Y(*cp+1)=(Y(*cp-1)+Y(1))/2;
			}
		}
	}
	return self;
}

- insertPoint:(int *)cp
{
	if(controlpts)
	{
		if(*cp>=0 && *cp<ncontrolpts)
		{
			int i;

			controlpts=(float *)realloc(controlpts, sizeof(float)*(++ncontrolpts)*2);
			for(i=ncontrolpts-1;i>*cp;i--) { X(i)=X(i-1);  Y(i)=Y(i-1); }
			++*cp;
			if(*cp<ncontrolpts-1)
			{	X(*cp)=(X(*cp-1)+X(*cp+1))/2; Y(*cp)=(Y(*cp+1)+Y(*cp-1))/2; }
			else
			{	X(*cp)=(X(*cp-1)+X(0))/2; Y(*cp)=(Y(*cp-1)+Y(0))/2; }
		}
	}
	return self;
}

- deletePoint:(int *)cp
{
	if(controlpts)
	{
		if(*cp>=0 && *cp<ncontrolpts && ncontrolpts>0)
		{
			int i;

			if(ncontrolpts>1)
			{
				for(i=*cp;i<ncontrolpts-1;i++) { X(i)=X(i+1);  Y(i)=Y(i+1); }
				controlpts=(float *)realloc(controlpts, sizeof(float)*(--ncontrolpts)*2);
			}
			else
			{ return self; }
			--*cp;
			if(*cp<0)	*cp=0;
		}
	}
	return self;
}

// by Ralf Suckow for Bezier curves
// added to Pencil: 26_10_94

- insertThreePoints:(int *)cp
{
	if(controlpts)
	{
		if(*cp>=0 && *cp<ncontrolpts)
		{
			int i;

            // go to a point on the path
			
			if (*cp % 3 == 1)
			  (*cp)--;
			else if (*cp % 3 == 2)
			  (*cp)++;
			
			if (*cp == ncontrolpts)
			  *cp = 0;
			  
            ncontrolpts += 3;
			controlpts = (float *) realloc (controlpts, sizeof (float) * (ncontrolpts) * 2);
			for (i = ncontrolpts - 1; i > *cp + 1; i--) {
			  X (i) = X (i-3); 
			  Y (i) = Y (i-3);
			}
			
			*cp += 3; // *cp is now the new point on the path,
			          // *cp - 1 and *cp + 1 are the rulers
			
			if ( *cp < ncontrolpts - 3) {
			    
				X (*cp    ) = (X (*cp - 3) + X (*cp + 3)) / 2;
				Y (*cp    ) = (Y (*cp - 3) + Y (*cp + 3)) / 2;
				X (*cp - 1) = (X (*cp - 3) + X (*cp    )) / 2;
			    Y (*cp - 1) = (Y (*cp - 3) + Y (*cp    )) / 2; 
				X (*cp + 1) = (X (*cp    ) + X (*cp + 3)) / 2;
				Y (*cp + 1) = (Y (*cp    ) + Y (*cp + 3)) / 2;
			}
			else {
			   
			    X (*cp    ) = (X (*cp - 3) + X (0  )) / 2;
				Y (*cp    ) = (Y (*cp - 3) + Y (0  )) / 2;
				X (*cp - 1) = (X (*cp - 3) + X (*cp)) / 2;
			    Y (*cp - 1) = (Y (*cp - 3) + Y (*cp)) / 2; 
				X (*cp + 1) = (X (*cp    ) + X (0  )) / 2;
				Y (*cp + 1) = (Y (*cp    ) + Y (0  )) / 2;
			}
		}
	}
	return self;
}

- alignThreePoints:(int *)cp
{
    int fixed1, fixed2, variable;
    float ffx, fvx, ffy, fvy, base, scale;

	if (!controlpts || *cp < 0 || *cp >= ncontrolpts || ncontrolpts < 3)
	  return self;

    variable = *cp; // moving the selected point
	  
    // look which points not to move
	  	
    if (*cp % 3 == 1) {
        fixed1 = *cp - 1;
		fixed2 = *cp - 2;
    }
    else if (*cp % 3 == 2) {
		fixed1 = *cp + 1;
		fixed2 = *cp + 2;
    }
    else {
	    fixed1 = *cp - 1;
		fixed2 = *cp + 1;
    }
	
    fixed1 = (fixed1 + ncontrolpts) % ncontrolpts; // wrap around
    fixed2 = (fixed2 + ncontrolpts) % ncontrolpts;
	  
    ffx = X (fixed2) - X (fixed1);
    fvx = X (variable) - X (fixed1);
    ffy = Y (fixed2) - Y (fixed1);
    fvy = Y (variable) - Y (fixed1);
    base = ffx * ffx + ffy * ffy;
	  
    if (base < 0.000001) {
        X (variable) = X (fixed1);
		Y (variable) = Y (fixed1);
    }
    else {
	    scale = (ffx * fvx + ffy * fvy) / base;
	    X (variable) = X (fixed1) + scale * ffx;
	    Y (variable) = Y (fixed1) + scale * ffy;
    }
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteTypes(stream,"*****ffffffffffffcccci", &methodname, &drawingmethod, &strokemethod, &fillmethod, &data, &c1[0], &c1[1], &c1[2], &c2[0], &c2[1], &c2[2], &linewidth, &tx, &ty, &phi, &sx, &sy, &transformed, &rotated, &translated, &scaled, &ncontrolpts);
	NXWriteRect(stream, &bounds);
	NXWriteArray(stream, "f", ncontrolpts*2, controlpts);
	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];
	 if(NXTypedStreamClassVersion(stream, "PencilGraphic")<1) 
	 {
		NXReadTypes(stream,"*****fffffffffffcccci", &methodname, &drawingmethod, &strokemethod, &fillmethod, &data, &c1[0], &c1[1], &c1[2], &c2[0], &c2[1], &c2[2], &linewidth, &tx, &ty, &phi, &sx, &transformed, &rotated, &translated, &scaled, &ncontrolpts);
		sy=sx;
	}
	else
		NXReadTypes(stream,"*****ffffffffffffcccci", &methodname, &drawingmethod, &strokemethod, &fillmethod, &data, &c1[0], &c1[1], &c1[2], &c2[0], &c2[1], &c2[2], &linewidth, &tx, &ty, &phi, &sx, &sy, &transformed, &rotated, &translated, &scaled, &ncontrolpts);

	NXReadRect(stream, &bounds);
	controlpts=(float *)malloc(sizeof(float)*2*ncontrolpts);
	NXReadArray(stream, "f", ncontrolpts*2, controlpts);
	return self;
}

- (void)writeType:(NXStream *)to
{
	NXPrintf(to,"0");
}

#define WRITE_STRING(a) if((a) && strcmp(a,"")) NXPrintf(to, "{%s} ", a); else NXPrintf(to, "{} ")

- (void)writeDescription:(NXStream *)to
{
	int i;
	
	[self writeType:(NXStream *)to];
	NXPrintf(to, " { ");
	WRITE_STRING(methodname);
	WRITE_STRING(drawingmethod);
	WRITE_STRING(strokemethod);
	WRITE_STRING(fillmethod);
	WRITE_STRING(data);
	NXPrintf(to, "%g %g %g %g %g %g %g %g %g %g %g %g %d } { %g %g %g %g } { ", c1[0], c1[1], c1[2], c2[0], c2[1], c2[2], linewidth, tx, ty, phi, sx, sy, ncontrolpts,bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
	for(i=0;i<ncontrolpts;i++) { NXPrintf(to, "%g %g ", controlpts[i*2], controlpts[i*2+1]); }
	NXPrintf(to, "}");
}

- (char *)giveDescription
{
	int len;
	int maxlen;
	char *buf;
	 
	if(result) NXCloseMemory(result, NX_TRUNCATEBUFFER);
	result=NXOpenMemory(NULL,0,NX_WRITEONLY);
	[self writeDescription:result];
	NXGetMemoryBuffer(result, &buf, &len, &maxlen);
	return buf;
}

#define NEWSTRING(a) read_brace_string(from, tmp); if(a) free(a); a=(char *)malloc(sizeof(char)*(strlen(tmp)+1)); strcpy(a,tmp)

- (void)initFromDescription:(NXStream *)from
{
	static char tmp[500];
	int i;
	
	NXScanf(from," ");
	NXGetc(from);	// the {
	NEWSTRING(methodname);
	NEWSTRING(drawingmethod);
	NEWSTRING(strokemethod);
	NEWSTRING(fillmethod);
	NEWSTRING(data);
	free(controlpts);
	ncontrolpts=0;
	controlpts=NULL;
	NXScanf(from," %f %f %f %f %f %f %f %f %f %f %f %f %d", &c1[0], &c1[1], &c1[2], &c2[0], &c2[1], &c2[2], &linewidth, &tx, &ty, &phi, &sx, &sy, &ncontrolpts);
	NXScanf(from," ");
	NXGetc(from);
	NXScanf(from," ");
	NXGetc(from);
	NXScanf(from, " %f %f %f %f", &bounds.origin.x, &bounds.origin.y, &bounds.size.width, &bounds.size.height);
	NXScanf(from," ");
	NXGetc(from);
	NXScanf(from," ");
	NXGetc(from);
	controlpts=(float *)malloc(sizeof(float)*2*ncontrolpts);
	for(i=0;i<ncontrolpts;i++) { NXScanf(from, " %f %f", &controlpts[i*2], &controlpts[i*2+1]); }
	NXScanf(from," ");
	NXGetc(from);
	if(fabs(phi)<=1e-6) { phi=0; rotated=NO; } else { rotated=YES; }
	if(hypot(tx,ty)<=1e-6) { tx=ty=0; translated=NO; } else { translated=YES; }
	if(fabs(sx-1.0)+fabs(sy-1.0)<1e-6) { sx=sy=1; scaled=NO; } else { scaled=YES; }
	transformed=(translated || scaled || rotated) ? YES:NO;
}

- select:(BOOL)yesno { selected=yesno; return self; }
- (BOOL)selected { return (BOOL)selected; }
- (void)drawIfNeeded:(NXRect *)re:(int)cp:(float)bsize
{ } // only used for redrawing EPS after a move...
@end