/*
Pencil V2.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 "Copyright" in the Help panel for a copy of the
license).
Pencil has a built-in TCL interpreter (tcl 7.3 by John Ousterhout). This is under a separate
license (see "Copyright").
*/
#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 A m[0]
#define B m[1]
#define C m[2]
#define D m[3]
#define TX m[4]
#define TY m[5]

void translation( float *m, float dtx, float dty) { TX+=dtx; TY+=dty; }

void scaling( float *m, float sx, float sy) { A*=sx; B*=sy; C*=sx; D*=sy; TX*=sx; TY*=sy; }

void rotation( float *m, float phi) {
	float c, s, r[6]; 
	
	c=cos(phi);
	s=sin(phi);
	
	r[0]=A*c-B*s;
	r[1]=A*s+B*c;
	r[2]=C*c-D*s;
	r[3]=C*s+D*c;
	r[4]=TX*c-TY*s;
	r[5]=TX*s+TY*c;
	m[0]=r[0];
	m[1]=r[1];
	m[2]=r[2];
	m[3]=r[3];
	m[4]=r[4];
	m[5]=r[5];
			
}

void initM( float *m) 
{
	m[0]=1; m[1]=0; m[2]=0; m[3]=1; m[4]=m[5]=0;
}

void applyInverse( float *m, NXPoint *p) {
	float x, y, det;

	x=p->x-TX;
	y=p->y-TY;
	if(det=A*D-B*C) {
	p->x=(x*D-C*y)/det;
	p->y=(-x*B+y*A)/det;
	}
}

void transformPt( float *m, float x, float y, float *dx, float *dy)
{
	*dx=A*x+C*y+TX;
	*dy=B*x+D*y+TY;
}

extern unsigned long long int layersVisible1;
extern unsigned long long int layersVisible2;
extern unsigned long long int layersSelectable1;
extern unsigned long long int layersSelectable2;

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

- (void)transformPoint:(NXPoint *)pt
{
	if(transformed)
	{
		applyInverse( m, pt);
	}
}

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

	dphi=angle(pt1->x-x,pt1->y-y,pt2->x-x,pt2->y-y);
	translation( m, -x, -y);
	rotation( m, dphi);
	translation( m, x, y);
	transformed=MATRIX;
}

- (void)rotateBy: (float) dphi: (float)x: (float)y
{
	translation( m, -x, -y);
	rotation( m, dphi);
	translation( m, x, y);
	transformed=MATRIX;
}

- (void)scaleCenter:(float)cx:(float)cy by:(float)scx:(float)scy
{
	translation( m, -cx, -cy);
	scaling( m, scx, scy);
	translation( m, cx, cy);
	transformed=MATRIX;
}

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

	if(layer<=63 ? (layersSelectable1&(1ULL<<layer)): (layersSelectable2&(1ULL<<(layer-64)))) {
	pt=te->location;
	if(pt.x<bounds.origin.x || pt.y<bounds.origin.y || pt.x>bounds.origin.x+bounds.size.width || pt.y>bounds.origin.y+bounds.size.height) return NO;
	[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);
	return val;
	}
	else
		return NO;
}

- (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 ");
	switch(transformed) {
		case TRANSLATION: PStranslate(TX, TY); break;
		case MATRIX: PSconcat(m); break;
	}
	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(layer<=63 ? (layersVisible1&(1ULL<<layer)): (layersVisible2&(1ULL<<(layer-64)))) {
	if(!re || NXIntersectsRect(&bounds, re))
	{
	if(globalOutlines)
	{
	PSgsave();
	switch(transformed) {
		case TRANSLATION: PStranslate(TX, TY); break;
		case MATRIX: PSconcat(m); break;
	}
	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
	{
	switch(transformed) {
		case TRANSLATION: PSgsave(); PStranslate(TX, TY); break;
		case MATRIX: PSgsave(); PSconcat(m); break;
	}
	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);
	if(transformed) PSgrestore();
	}
	}
	}
}

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

	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];
	transformPt( m, llx, lly, &bounds.origin.x, &bounds.origin.y);
	bounds.size.width=bounds.size.height=0;
	transformPt( m, llx, ury, &nx, &ny);
	expandRect(&bounds, nx, ny);
	transformPt( m, urx, ury, &nx, &ny);
	expandRect(&bounds, nx, ny);
	transformPt( m,urx, lly, &nx, &ny);
	expandRect(&bounds, nx, ny);
	return self;
}

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

- (void)drawControl:(NXRect *)re:(int)cp:(float)bsize
{
	[self draw:re];
	switch(transformed) {
		case TRANSLATION: PSgsave(); PStranslate(TX, TY); break;
		case MATRIX: PSgsave(); PSconcat(m); bsize/=hypot(m[0], m[1]); break;
	}
	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;
	initM( m);
	transformed=NO;
	layer=0;
	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;
	initM( m);
	transformed=NO;
	layer=0;
	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
{
	translation(m, dtx, dty);
	if(!transformed) transformed=TRANSLATION;
	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
{
	unsigned char ly=layer;
	
	[super write:stream];
	// pre PencilTWO:
	/*
	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);
	*/
	// PencilTWO >>
	NXWriteTypes(stream,"*****fffffffcci", &methodname, &drawingmethod, &strokemethod, &fillmethod, &data, &c1[0], &c1[1], &c1[2], &c2[0], &c2[1], &c2[2], &linewidth, &transformed, &ly, &ncontrolpts);
	NXWriteArray(stream, "f", 6, m);
	// << PencilTWO
	NXWriteRect(stream, &bounds);
	NXWriteArray(stream, "f", ncontrolpts*2, controlpts);
	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];
	 switch(NXTypedStreamClassVersion(stream, "PencilGraphic")) 
	 {
	case 0:
	case 1:
	{
		float tx, ty, phi, sx, sy;
		char rotated, translated, scaled;
		
		if(NXTypedStreamClassVersion(stream, "PencilGraphic")==0) {
		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);
		}
		if(scaled || rotated) transformed=MATRIX;
			else if(translated) transformed=TRANSLATION;
		initM(m);
		scaling( m, sx, sy);
		rotation( m, phi);
		translation( m, tx, ty);
		layer=0;
	}
	break;
	case 2:
	{
	unsigned char ly;
	NXReadTypes(stream,"*****fffffffcci", &methodname, &drawingmethod, &strokemethod, &fillmethod, &data, &c1[0], &c1[1], &c1[2], &c2[0], &c2[1], &c2[2], &linewidth, &transformed, &ly, &ncontrolpts);
	layer=ly;
	NXReadArray(stream, "f", 6, m);
	}
	break;
	}
	NXReadRect(stream, &bounds);
	controlpts=(float *)malloc(sizeof(float)*2*ncontrolpts);
	NXReadArray(stream, "f", ncontrolpts*2, controlpts);
	return self;
}

- (int)type
{
	return 0;
}

#define APPFLOAT(a) sprintf(string,"%g",a); Tcl_AppendElement(interp,string)
#define APPINT(a) sprintf(string,"%d",a); Tcl_AppendElement(interp,string)

- (void)giveDescription: (Tcl_Interp *)interp
{
	static char string[20];
	int i;
	
	sprintf( string, "%d", [self type]);
	Tcl_AppendElement( interp, string);
	Tcl_AppendElement( interp, methodname);
	Tcl_AppendElement( interp, drawingmethod);
	Tcl_AppendElement( interp, strokemethod);
	Tcl_AppendElement( interp, fillmethod);
	Tcl_AppendElement( interp, data);
	APPINT(ncontrolpts);
	Tcl_AppendResult( interp," {", NULL);
	for(i=0;i<ncontrolpts*2;i++) { APPFLOAT(controlpts[i]); }
	Tcl_AppendResult( interp,"} {", NULL);
	APPFLOAT(bounds.origin.x);
	APPFLOAT(bounds.origin.y);
	APPFLOAT(bounds.size.width);
	APPFLOAT(bounds.size.height);
	Tcl_AppendResult( interp,"} ", NULL);
	Tcl_AppendResult( interp," {", NULL);
	for(i=0;i<6;i++) { APPFLOAT(m[i]); }
	Tcl_AppendResult( interp,"}", NULL);
	APPFLOAT(c1[0]);
	APPFLOAT(c1[1]);
	APPFLOAT(c1[2]);
	APPFLOAT(c2[0]);
	APPFLOAT(c2[1]);
	APPFLOAT(c2[2]);
	APPFLOAT(linewidth);
	APPINT(layer);
}

#define NEWSTRING(a,b) if(a) free(a); a=(char *)malloc(sizeof(char)*(strlen(b)+1)); strcpy(a, b);
#define GFLOAT(a,b) a=atof(argv[b])

- (void)initFromDescription: (Tcl_Interp *)interp: (char *)desc
{
	int argc;
	char **argv;
	int slc;
	char **slv;
	int i;
	
	Tcl_SplitList( interp, desc, &argc, &argv);
	if(argc>=18) {
	NEWSTRING(methodname, argv[1]);
	NEWSTRING(drawingmethod, argv[2]);
	NEWSTRING(strokemethod, argv[3]);
	NEWSTRING(fillmethod, argv[4]);
	NEWSTRING(data, argv[5]);
	if(controlpts) free(controlpts);	
	controlpts=(float *)malloc(sizeof(float)*2*(ncontrolpts=atoi(argv[6])));
	Tcl_SplitList( interp, argv[7], &slc, &slv); // controlpoints
	if(slc<ncontrolpts*2) ncontrolpts=slc/2;
	for(i=0;i<ncontrolpts*2;i++) controlpts[i]=atof(slv[i]);
	free(slv);
	Tcl_SplitList( interp, argv[9], &slc, &slv); // matrix
	for(i=0;i<slc;i++) m[i]=atof(slv[i]);
	free(slv);
	if(m[0]!=1 || m[1]!=0 || m[2]!=0 || m[3]!=1)
		transformed=MATRIX;
	else
		if(m[4]!=0 || m[5]!=0)
			transformed=TRANSLATION;
		else
			transformed=NO;
	GFLOAT(c1[0], 10);
	GFLOAT(c1[1], 11);
	GFLOAT(c1[2], 12);
	GFLOAT(c2[0], 13);
	GFLOAT(c2[1], 14);
	GFLOAT(c2[2], 15);
	GFLOAT(linewidth, 16);
	layer=atoi( argv[17]);
	[self specialDescription: interp: argc: argv];
	}
	free(argv);
}

- (void)specialDescription: (Tcl_Interp *)interp: (int)argc: (char **)argv
{}

- select:(BOOL)yesno { selected=yesno; return self; }
- (BOOL)selected { return (BOOL)selected; }
- (BOOL)selectable { 	if(layer<=63 ? (layersSelectable1&(1ULL<<layer)): (layersSelectable2&(1ULL<<(layer-64)))) return YES; else return NO; }
- (void)drawIfNeeded:(NXRect *)re:(int)cp:(float)bsize
{ } // only used for redrawing EPS after a move...
- setLayer: (unsigned char) ly { layer=ly; return self; }
- (unsigned char) getLayer { return layer; }
@end