/* xdraw.c 
 *    by Nathan Laredo (laredo@gnu.org)
 *
 * If you didn't find a GPL here, it should have been here.
 *
 * gcc -Wall -O2 -o xdraw xdraw.c -L/usr/X11R6/lib -lX11
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Intrinsic.h>

static int segment[32768];	/* start index of each segment */
static XPoint points[65536];	/* points in each line segment */
static float real_x[65536];	/* real x value */
static float real_y[65536];	/* real y value */
static int numsegments;		/* number of line segments */
static int xmin, xmax, ymin, ymax;	/* for clipping */
static int reject = 0;		/* rejecting point flag */

/* current transform matrix */
static float a = 1.0, b = 0.0, c = 0.0, d = 1.0, u = 0.0, v = 0.0;

Display *mydisplay;
Window mywindow;
GC mygc;
XEvent myevent;
char tmpbuf[128];

void init_display(int argc, char **argv)
{
	unsigned long myforeground, mybackground;
	XSizeHints myhint;
	XGCValues gcvals;
	int myscreen;

	if (!getenv("DISPLAY")) {
		fprintf(stderr, "DISPLAY not set.\n");
		exit(1);
	}
	if (!(mydisplay = XOpenDisplay(getenv("DISPLAY")))) {
		fprintf(stderr, "Unable to open display.\n");
		exit(1);
	}
	myscreen = DefaultScreen(mydisplay);
	mybackground = WhitePixel(mydisplay, myscreen);
	myforeground = BlackPixel(mydisplay, myscreen);
	myhint.width = 612;
	myhint.height = 792;
	myhint.x = myhint.y = 0;
	myhint.max_width = 612;
	myhint.max_height = 792;
	myhint.min_width = 612;
	myhint.min_height = 792;
	myhint.flags = PSize | PMinSize | PMaxSize | PPosition;
	mywindow = XCreateSimpleWindow(mydisplay,
		DefaultRootWindow(mydisplay), myhint.x, myhint.y,
		myhint.width, myhint.height, 0, 0, mybackground);
	sprintf(tmpbuf, "xdraw");
	XSetStandardProperties(mydisplay, mywindow, tmpbuf, tmpbuf, None,
			       argv, argc, &myhint);
	gcvals.foreground = myforeground;
	gcvals.background = mybackground;
	mygc = XCreateGC(mydisplay, mywindow, GCForeground | GCBackground,
			&gcvals);
	XSelectInput(mydisplay, mywindow, ExposureMask | ButtonPressMask |
		     ButtonReleaseMask | ButtonMotionMask);
	XMapRaised(mydisplay, mywindow);
	xmin = ymin = 0;
	xmax = myhint.width;
	ymax = myhint.height;
}

static void transform_point(int index)
{
	/* CTM:
	 *
	 * | a  b  0 |
	 * | c  d  0 |
	 * | u  v  1 |
	 *
	 * In postscript: [ a b c d u v ]
	 * x' = ax + cy + u
	 * y' = bx + dy + v
	 */
	float x = real_x[index], y = real_y[index], X, Y;

	X = a * x + c * y + u;
	Y = b * x + d * y + v;
	points[index].x = X;
	points[index].y = Y;
}

static void inverse_transform_point(int index)
{
	/* CTM operation:
	 * x' = ax + cy + u
	 * y' = bx + dy + v
	 *
	 * Inverse of CTM operation:
	 * u' = u - x'
	 * v' = v - y'
	 * y = (bu' - av') / (ad - bc)
	 * x = (cv' - u'd) / (ad - bc)
	 */
	float X = points[index].x, Y = points[index].y, x, y, U, V;
	float k = (a * d - b * c);

	U = u - X;
	V = v - Y;
	y = (b * U - a * V) / k;
	x = (c * V - U * d) / k;
	real_x[index] = x;
	real_y[index] = y;
}

static void print_data(void)
{
	static int page = 0;
	int i, j, pos = 0;
	FILE *psfile;

	if (!(psfile = fopen("/tmp/xd.ps", "a"))) {
		perror("/tmp/xd.ps");
		psfile = stderr;
	};

	page++;
	fprintf(psfile, "%%!PS\n"
			"%%%%Page: %d\n"
			"[ %.3f %.3f %.3f %.3f %.3f %.3f ] pop\n"
			"1 -1 scale 0 -792 translate\n",
			page, a, b, c, d, u, v);
	fprintf(psfile, "/L { lineto } bind def\n"
			"/M { moveto } bind def\n"
			"/N { newpath } bind def\n"
			"/S { stroke } bind def\n");
	for (i = 1; i < numsegments; i++) {
		if (segment[i + 1] <= segment[i])
			break;
		j = segment[i];
		pos += fprintf(psfile, "N %.3f %.3f M ", 
			real_x[j], real_y[j]);
		for (; j < segment[i + 1]; j++) {
			if (pos > 102) {
				/* keep lines short */
				fprintf(psfile, "\n");
				pos = 0;
			}
			pos += fprintf(psfile, "%.3f %.3f L ",
				real_x[j], real_y[j]);
		}
		fprintf(psfile, "S\n");
		pos = 0;
	}
	fprintf(psfile, "showpage\n");
	if (psfile != stderr)
		fclose(psfile);
}

static void handle_expose_event(void)
{
	int i;

	for (i = 0; i < numsegments; i++) {
		if (segment[i + 1] <= segment[i])
			break;
		XDrawLines(mydisplay, mywindow, mygc, &points[segment[i]],
		    segment[i + 1] - segment[i], CoordModeOrigin);
	}
}

int xbase = 0, ybase = 0;
static void handle_button_press(void)
{
	xbase = myevent.xbutton.x;
	ybase = myevent.xbutton.y;
	if (myevent.xbutton.button == Button3 && (myevent.xbutton.state&15)) {
		print_data();
		return;
	}
	if (myevent.xbutton.button == Button3) {
		if (numsegments > 1)
			numsegments--;
		XClearArea(mydisplay, mywindow, 0, 0, 0, 0, True);
		return;
	}
	if (myevent.xbutton.button == Button1) {
		int i = numsegments;
		int index = segment[numsegments];
		points[index].x = myevent.xmotion.x;
		points[index].y = myevent.xmotion.y;
		inverse_transform_point(index);	/* store real point */
		if (real_x[index] > 612.0 || real_y[index] > 792.0 ||
		    real_x[index] < 0.0 || real_y[index] < 0.0) {
			reject++;
			return;		/* reject point */
		}
		reject = 0;
		numsegments++;
		segment[numsegments]++;
		XDrawLines(mydisplay, mywindow, mygc, &points[segment[i]],
		    segment[i + 1] - segment[i], CoordModeOrigin);
		return;
	}
	if (myevent.xbutton.button == Button5 && (myevent.xbutton.state&15)) {
		int i;
		float co, si, A, B, C, D;

		co = cos(M_PI/45);
		si = sin(M_PI/45);
		/* Matrix multiply:
		 * | a  b  0 |   | co si 0 | 
		 * | c  d  0 | x |-si co 0 |
		 * | u  v  1 |   |  0  0 1 |
		 */
		A = a * co - b * si;
		B = a * si + b * co;
		C = c * co - d * si;
		D = c * si + d * co;
		a = A; b = B; c = C; d = D;
		for (i = 0; i < segment[numsegments]; i++)
			transform_point(i);
		XClearArea(mydisplay, mywindow, 0, 0, 0, 0, True);
		return;
	}
	if (myevent.xbutton.button == Button4 && (myevent.xbutton.state&15)) {
		int i;
		float co, si, A, B, C, D;

		co = cos(-M_PI/45);
		si = sin(-M_PI/45);
		A = a * co - b * si;
		B = a * si + b * co;
		C = c * co - d * si;
		D = c * si + d * co;
		a = A; b = B; c = C; d = D;
		for (i = 0; i < segment[numsegments]; i++)
			transform_point(i);
		XClearArea(mydisplay, mywindow, 0, 0, 0, 0, True);
		return;
	}
	if (myevent.xbutton.button == Button5) {
		int i;
		/* Matrix multiply:
		 * | a b 0 |   | s 0 0 | 
		 * | c d 0 | x | 0 s 0 |
		 * | u v 1 |   | 0 0 1 |
		 */
		a *= 1.1;
		b *= 1.1;
		c *= 1.1;
		d *= 1.1;
		for (i = 0; i < segment[numsegments]; i++)
			transform_point(i);
		XClearArea(mydisplay, mywindow, 0, 0, 0, 0, True);
		return;
	}
	if (myevent.xbutton.button == Button4) {
		int i;
		a *= 1.0/1.1;
		b *= 1.0/1.1;
		c *= 1.0/1.1;
		d *= 1.0/1.1;
		for (i = 0; i < segment[numsegments]; i++)
			transform_point(i);
		XClearArea(mydisplay, mywindow, 0, 0, 0, 0, True);
		return;
	}
}

static void handle_motion_event(void)
{
	if (myevent.xmotion.state & Button1Mask) {
		int i = numsegments - 1;
		int index = segment[numsegments];
		points[index].x = myevent.xmotion.x;
		points[index].y = myevent.xmotion.y;
		inverse_transform_point(index);
		if (real_x[index] > 612.0 || real_y[index] > 792.0 ||
		    real_x[index] < 0.0 || real_y[index] < 0.0) {
			segment[numsegments + 1] = segment[numsegments];
			reject++;
		} else {
			if (reject) {	/* back inside from outside */
				i++;
				numsegments++;
			}
			reject = 0;
			segment[numsegments]++;
			XDrawLines(mydisplay, mywindow, mygc,
				&points[segment[i]],
				segment[i + 1] - segment[i], CoordModeOrigin);
		}
	}
	if (myevent.xmotion.state & Button2Mask) {
		int i;
		u += myevent.xmotion.x - xbase;
		v += myevent.xmotion.y - ybase;
		for (i = 0; i < segment[numsegments]; i++)
			transform_point(i);
		XClearArea(mydisplay, mywindow, 0, 0, 0, 0, False);
		handle_expose_event();
	}
	xbase = myevent.xmotion.x;
	ybase = myevent.xmotion.y;
}

static void handle_button_release(void)
{
	segment[numsegments + 1] = segment[numsegments];
	reject = 0;
}

int main(int argc, char **argv)
{
	/* draw bounding box */
	real_x[0] = real_x[1] = real_y[0] = real_y[3] =
		real_x[4] = real_y[4] = -1.0;
	real_y[1] = real_y[2] = 792.0;
	real_x[2] = real_x[3] = 612.0;
	for (numsegments = 0; numsegments < 5; numsegments++) {
		transform_point(numsegments);
		segment[numsegments] = 0;
	}
	numsegments = 1;
	segment[1] = segment[2] = 5;	/* bounding box is 4 points */
	init_display(argc, argv);
	while (numsegments < 32768 && segment[numsegments] < 65536) {
		XNextEvent(mydisplay, &myevent);
		switch (myevent.type) {
		case Expose:
			handle_expose_event();
			break;
		case MotionNotify:
			handle_motion_event();
			break;
		case ButtonPress:
			handle_button_press();
			break;
		case ButtonRelease:
			handle_button_release();
			break;
		default:
			fprintf(stderr, "unexpected event 0x%x\n",
				myevent.type);
		}
	}
	print_data();
	exit(0);
}
