/*
* 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   (double) (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 [email protected]
*/

/*
*      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);
}