/*
 * Support drawing routines for TeXsun and TeX
 *
 *      Copyright, (C) 1987, 1988 Tim Morgan, UC Irvine
 *
 * At the time these routines are called, the values of hh and vv should
 * have been updated to the upper left corner of the graph (the position
 * the \special appears at in the dvi file).  Then the coordinates in the
 * graphics commands are in terms of a virtual page with axes oriented the
 * same as the Imagen and the SUN normally have:
 *
 *                      0,0
 *                       +-----------> +x
 *                       |
 *                       |
 *                       |
 *                      \ /
 *                       +y
 *
 * Angles are measured in the conventional way, from +x towards +y.
 * Unfortunately, that reverses the meaning of "counterclockwise"
 * from what it's normally thought of.
 *
 * A lot of floating point arithmetic has been converted to integer
 * arithmetic for speed.  In some places, this is kind-of kludgy, but
 * it's worth it.
 */

#include <math.h>
#include <ctype.h>

#include "xdvi.h"

#ifndef	X_NOT_STDC_ENV
#include <stdlib.h>
#endif

#define	MAXPOINTS	300	/* Max points in a path */
#define	TWOPI		(3.14159265359*2.0)
#define	MAX_PEN_SIZE	7	/* Max pixels of pen width */


static	int	xx[MAXPOINTS], yy[MAXPOINTS];	/* Path in milli-inches */
static	int	path_len = 0;	/* # points in current path */
static	int	pen_size = 1;	/* Pixel width of lines drawn */

static	Boolean	whiten = False;
static	Boolean	shade = False;
static	Boolean	blacken = False;

/* Unfortunately, these values also appear in dvisun.c */
#define	xRESOLUTION	(pixels_per_inch/shrink_factor)
#define	yRESOLUTION	(pixels_per_inch/shrink_factor)


/*
 * Issue warning messages
 */
static	void
Warning(fmt, msg)
	char	*fmt, *msg;
{
	Fprintf(stderr, fmt, msg);
	(void) fputc('\n', stderr);
}


/*
 * Set the size of the virtual pen used to draw in milli-inches
 */
/* ARGSUSED */
static	void
set_pen_size(cp)
	char *cp;
{
	int	ps;

	if (sscanf(cp, " %d ", &ps) != 1) {
	    Warning("illegal .ps command format: %s", cp);
	    return;
	}
	pen_size = (ps * (xRESOLUTION + yRESOLUTION) + 1000) / 2000;
	if (pen_size < 1) pen_size = 1;
	else if (pen_size > MAX_PEN_SIZE) pen_size = MAX_PEN_SIZE;
}


/*
 * Print the line defined by previous path commands
 */
static	void
flush_path()
{
	register int i;
	int	last_min_x, last_max_x, last_min_y, last_max_y;

	last_min_x = 30000;
	last_min_y = 30000;
	last_max_x = -30000;
	last_max_y = -30000;
	for (i = 1; i < path_len; i++) {
	    if (xx[i] > last_max_x) last_max_x = xx[i];
	    if (xx[i] < last_min_x) last_min_x = xx[i];
	    if (yy[i] > last_max_y) last_max_y = yy[i];
	    if (yy[i] < last_min_y) last_min_y = yy[i];
	    line_btw(xx[i], yy[i], xx[i+1], yy[i+1]);
	}
	if (xx[path_len] > last_max_x) last_max_x = xx[path_len];
	if (xx[path_len] < last_min_x) last_min_x = xx[path_len];
	if (yy[path_len] > last_max_y) last_max_y = yy[path_len];
	if (yy[path_len] < last_min_y) last_min_y = yy[path_len];
	path_len = 0;
	do_attribute_path(last_min_x, last_max_x, last_min_y, last_max_y);
}


/*
 * Print a dashed line along the previously defined path, with
 * the dashes/inch defined.
 */

static	void
flush_dashed(cp, dotted)
	char	*cp;
	int	dotted;
{
	int	i;
	int	numdots;
	int	lx0, ly0, lx1, ly1;
	int	cx0, cy0, cx1, cy1;
	float	inchesperdash;
	double	d, spacesize, a, b, dx, dy, milliperdash;

	if (sscanf(cp, " %f ", &inchesperdash) != 1) {
	    Warning("illegal format for dotted/dashed line: %s", cp);
	    return;
	}
	if (path_len <= 1 || inchesperdash <= 0.0) {
	    Warning("illegal conditions for dotted/dashed line", "");
	    return;
	}
	milliperdash = inchesperdash * 1000.0;
	lx0 = xx[1];	ly0 = yy[1];
	lx1 = xx[2];	ly1 = yy[2];
	dx = lx1 - lx0;
	dy = ly1 - ly0;
	if (dotted) {
	    numdots = sqrt(dx*dx + dy*dy) / milliperdash + 0.5;
	    if (numdots == 0) numdots = 1;
	    for (i = 0; i <= numdots; i++) {
		a = (float) i / (float) numdots;
		cx0 = lx0 + a * dx + 0.5;
		cy0 = ly0 + a * dy + 0.5;
		dot_at(cx0, cy0);
	    }
	}
	else {
	    d = sqrt(dx*dx + dy*dy);
	    numdots = d / (2.0 * milliperdash) + 1.0;
	    if (numdots <= 1)
		line_btw(lx0, ly0, lx1, ly1);
	    else {
		spacesize = (d - numdots * milliperdash) / (numdots - 1);
		for (i = 0; i < numdots - 1; i++) {
		    a = i * (milliperdash + spacesize) / d;
		    b = a + milliperdash / d;
		    cx0 = lx0 + a * dx + 0.5;
		    cy0 = ly0 + a * dy + 0.5;
		    cx1 = lx0 + b * dx + 0.5;
		    cy1 = ly0 + b * dy + 0.5;
		    line_btw(cx0, cy0, cx1, cy1);
		    b += spacesize / d;
		}
		cx0 = lx0 + b * dx + 0.5;
		cy0 = ly0 + b * dy + 0.5;
		line_btw(cx0, cy0, lx1, ly1);
	    }
	}
	path_len = 0;
}


/*
 * Add a point to the current path
 */
static	void
add_path(cp)
	char	*cp;
{
	int	pathx, pathy;

	if (++path_len >= MAXPOINTS) oops("Too many points");
	if (sscanf(cp, " %d %d ", &pathx, &pathy) != 2)
	    oops("Malformed path command");
	xx[path_len] = pathx;
	yy[path_len] = pathy;
}


/*
 * Draw to a floating point position
 */
static void
im_fdraw(x, y)
	double	x,y;
{
	if (++path_len >= MAXPOINTS) oops("Too many arc points");
	xx[path_len] = x + 0.5;
	yy[path_len] = y + 0.5;
}


/*
 * Draw an ellipse with the indicated center and radices.
 */
static	void
draw_ellipse(xc, yc, xr, yr)
	int	xc, yc, xr, yr;
{
	double	angle, theta;
	int	n;
	int	px0, py0, px1, py1;

	angle = (xr + yr) / 2.0;
	theta = sqrt(1.0 / angle);
	n = TWOPI / theta + 0.5;
	if (n < 12) n = 12;
	else if (n > 80) n = 80;
	n /= 2;
	theta = TWOPI / n;

	angle = 0.0;
	px0 = xc + xr;		/* cos(0) = 1 */
	py0 = yc;		/* sin(0) = 0 */
	while ((angle += theta) <= TWOPI) {
	    px1 = xc + xr*cos(angle) + 0.5;
	    py1 = yc + yr*sin(angle) + 0.5;
	    line_btw(px0, py0, px1, py1);
	    px0 = px1;
	    py0 = py1;
	}
	line_btw(px0, py0, xc + xr, yc);
}

/*
 * Draw an arc
 */
static	void
arc(cp, invis)
	char	*cp;
	Boolean	invis;
{
	int	xc, yc, xrad, yrad, n;
	float	start_angle, end_angle, angle, theta, r;
	double	xradius, yradius, xcenter, ycenter;

	if (sscanf(cp, " %d %d %d %d %f %f ", &xc, &yc, &xrad, &yrad,
		&start_angle, &end_angle) != 6) {
	    Warning("illegal arc specification: %s", cp);
	    return;
	}

	if (invis) return;

	/* We have a specialized fast way to draw closed circles/ellipses */
	if (start_angle <= 0.0 && end_angle >= 6.282) {
	    draw_ellipse(xc, yc, xrad, yrad);
	    return;
	}
	xcenter = xc;
	ycenter = yc;
	xradius = xrad;
	yradius = yrad;
	r = (xradius + yradius) / 2.0;
	theta = sqrt(1.0 / r);
	n = 0.3 * TWOPI / theta + 0.5;
	if (n < 12) n = 12;
	else if (n > 80) n = 80;
	n /= 2;
	theta = TWOPI / n;
	flush_path();
	im_fdraw(xcenter + xradius * cos(start_angle),
	    ycenter + yradius * sin(start_angle));
	angle = start_angle + theta;
	if (end_angle < angle) end_angle += TWOPI;
	while (angle < end_angle) {
	    im_fdraw(xcenter + xradius * cos(angle),
		ycenter + yradius * sin(angle));
	    angle += theta;
	}
	im_fdraw(xcenter + xradius * cos(end_angle),
	    ycenter + yradius * sin(end_angle));
	flush_path();
}


/*
 * APPROXIMATE integer distance between two points
 */
#define	dist(x0, y0, x1, y1)	(abs(x0-x1)+abs(y0-y1))


/*
 * Draw a spline along the previously defined path
 */
static	void
flush_spline()
{
	int	xp, yp;
	int	N;
	int	lastx, lasty;
	int	t1, t2, t3;
	int	steps;
	int	j;
	register int i, w;

#ifdef	lint
	lastx = lasty = -1;
#endif
	N = path_len + 1;
	xx[0] = xx[1];
	yy[0] = yy[1];
	xx[N] = xx[N-1];
	yy[N] = yy[N-1];
	for (i = 0; i < N - 1; i++) {	/* interval */
	    steps = (dist(xx[i], yy[i], xx[i+1], yy[i+1]) +
		dist(xx[i+1], yy[i+1], xx[i+2], yy[i+2])) / 80;
	    for (j = 0; j < steps; j++) {	/* points within */
		w = (j * 1000 + 500) / steps;
		t1 = w * w / 20;
		w -= 500;
		t2 = (750000 - w * w) / 10;
		w -= 500;
		t3 = w * w / 20;
		xp = (t1*xx[i+2] + t2*xx[i+1] + t3*xx[i] + 50000) / 100000;
		yp = (t1*yy[i+2] + t2*yy[i+1] + t3*yy[i] + 50000) / 100000;
		if (i != 0 || j != 0) line_btw(lastx, lasty, xp, yp);
		lastx = xp;
		lasty = yp;
	    }
	}
	path_len = 0;
}


/*
 * Shade the last box, circle, or ellipse
 */
static	void
shade_last()
{
	blacken = whiten = False;
	shade = True;
}


/*
 * Make the last box, circle, or ellipse, white inside (shade with white)
 */
static	void
whiten_last()
{
	whiten = True;
	blacken = shade = False;
}


/*
 * Make last box, etc, black inside
 */
static	void
blacken_last()
{
	blacken = True;
	whiten = shade = False;
}


/*
 *	The following copyright message applies to the rest of this file.  --PV
 */

/*
 *	This program is Copyright (C) 1987 by the Board of Trustees of the
 *	University of Illinois, and by the author Dirk Grunwald.
 *
 *	This program may be freely copied, as long as this copyright
 *	message remaines affixed. It may not be sold, although it may
 *	be distributed with other software which is sold. If the
 *	software is distributed, the source code must be made available.
 *
 *	No warranty, expressed or implied, is given with this software.
 *	It is presented in the hope that it will prove useful.
 *
 *	Hacked in ignorance and desperation by jonah@db.toronto.edu
 */

/*
 *      The code to handle the \specials generated by tpic was modified
 *      by Dirk Grunwald using the code Tim Morgan at Univ. of Calif, Irvine
 *      wrote for TeXsun.
 */

#define	COMLEN	4

void
applicationDoSpecial(cp)
	char	*cp;
{
	char	command[COMLEN], *orig_cp;
	register int len;

	orig_cp = cp;
	while (isspace(*cp)) ++cp;
	len = 0;
	while (!isspace(*cp) && *cp && len < COMLEN-1) command[len++] = *cp++;
	command[len] = '\0';
	if (strcmp(command, "pn") == 0) set_pen_size(cp);
	else if (strcmp(command, "fp") == 0) flush_path();
	else if (strcmp(command, "da") == 0) flush_dashed(cp, 0);
	else if (strcmp(command, "dt") == 0) flush_dashed(cp, 1);
	else if (strcmp(command, "pa") == 0) add_path(cp);
	else if (strcmp(command, "ar") == 0) arc(cp, False);
	else if (strcmp(command, "ia") == 0) arc(cp, True);
	else if (strcmp(command, "sp") == 0) flush_spline();
	else if (strcmp(command, "sh") == 0) shade_last();
	else if (strcmp(command, "wh") == 0) whiten_last();
	else if (strcmp(command, "bk") == 0) blacken_last();
	/* throw away the path -- jansteen */
	else if (strcmp(command, "ip") == 0) path_len = 0;
	else if (!hush_spec_now)
	    Fprintf(stderr, "%s:  special \"%s\" not implemented\n", prog,
		orig_cp);
}
