/*========================================================================*\

Copyright (c) 1990-2004  Paul Vojta

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE:
This module is based on prior work as noted below.

\*========================================================================*/

/*
 * Support drawing routines for TeXsun and TeX
 *
 *      Copyright, (C) 1987, 1988 Tim Morgan, UC Irvine
 *	Adapted for xdvi by Jeffrey Lee, U. of Toronto
 *
 * 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 <setjmp.h>

#include "xdvi-config.h"
#include "xdvi.h"

#include <ctype.h>

#include "kpathsea/c-fopen.h"
#include "kpathsea/c-stat.h"
#include "kpathsea/line.h"
#include "kpathsea/tex-file.h"

#include "special.h"
#include "hypertex.h"
#include "dvi.h"
#include "message-window.h"
#include "events.h"
#include "dvi-init.h"
#include "dvi-draw.h"
#include "statusline.h"
#include "util.h"
#include "image-magick.h"
#include "pagesel.h"
#include "my-snprintf.h"
#include "string-utils.h"

#if PS
# ifdef	PS_DPS
# include "psdps.h"
# endif
# ifdef	PS_NEWS
# include "psnews.h"
# endif
# ifdef	PS_GS
# include "psgs.h"
# endif

/*
  Code inside BUG_888087_FIXED improve the appearance of pic specials at magstep 1, but
  _decreases_ it at other magnifications (bug #888087; see
  http://sourceforge.net/tracker/index.php?func=detail&aid=888087&group_id=23164&atid=377580)
  Disabled until this is fixed.
*/
#define BUG_888087_FIXED 0


/*
  Flag whether this page contains raw PS material;
  used to warn user about this:
*/
Boolean have_raw_postscript = False;

# if PS_GS && GS_PIXMAP_CLEARING_HACK
/*
  Flag whether this page contains PS material;
  used to flush the GS buffer
*/
Boolean had_ps_specials = False;
# endif /* PS_GS && GS_PIXMAP_CLEARING_HACK */

#endif


#if COLOR

/*
 *	Color stack used when scanning.  These records stay around, to ease
 *	the burden on xmalloc().  The first entry is a dummy entry, giving
 *	the default foreground color (as modified).
 */

struct colorframe {
    struct colorframe *next, *prev;
    struct rgb color;
};

static Boolean m_have_papersize_special = False;

void reset_papersize_special(void)
{
    m_have_papersize_special = False;
}

Boolean have_papersize_special(void)
{
    return m_have_papersize_special;
}

static struct colorframe scanstack_head;
static int scanstack_len;
static struct colorframe *scanstack_current;

/*
 *	Page stack when displaying.  Again, the records stay around once
 *	allocated.  Bottom entries in the stack (inherited from previous
 *	pages) are stored in an array instead (see comments in xdvi.h).
 */

static struct colorframe *rcs_head;

static Boolean parse_color (const char *cp0, const char *cp, struct rgb *rgbp, Boolean generic_ps_flag);

#endif /* COLOR */


#ifndef S_IRUSR
# define S_IRUSR 0400
#endif
#ifndef S_IWUSR
# define S_IWUSR 0200
#endif

# if HAVE_SYS_WAIT_H
#  include <sys/wait.h>
# endif
# ifndef WEXITSTATUS
#  define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
# endif
# ifndef WIFEXITED
#  define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
# endif

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

extern double floor(double);
#define	rint(x)	floor((x) + 0.5)

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;

Boolean psfig_begun = False;

#define	CMD(x, y)	((x) << 8 | (y))


/*
 *	X drawing routines
 */

#define	toint(x)	((int) ((x) + 0.5))
#define	xconv(x)	(toint(tpic_conv*(x))/currwin.shrinkfactor + PXL_H)
#define	yconv(y)	(toint(tpic_conv*(y))/currwin.shrinkfactor + PXL_V)

/*
 *	Draw a line from (fx,fy) to (tx,ty).
 */
static void
line_btw(int fx, int fy, int tx, int ty)
{
    int fcx = xconv(fx);
    int tcx = xconv(tx);
    int fcy = yconv(fy);
    int tcy = yconv(ty);

    if ((fcx < globals.win_expose.max_x || tcx < globals.win_expose.max_x) && (fcx >= globals.win_expose.min_x || tcx >= globals.win_expose.min_x) &&
	(fcy < globals.win_expose.max_y || tcy < globals.win_expose.max_y) && (fcy >= globals.win_expose.min_y || tcy >= globals.win_expose.min_y)) {
#if COLOR
	if (fg_active != fg_current)
	    do_color_change();
#endif
	XDrawLine(DISP, currwin.win, globals.gc.rule,
		  fcx - currwin.base_x, fcy - currwin.base_y,
		  tcx - currwin.base_x, tcy - currwin.base_y);
    }
}

/*
 *	Draw a dot at (x,y)
 */
static void
dot_at(int x, int y)
{
    int cx = xconv(x);
    int cy = yconv(y);

    if (cx < globals.win_expose.max_x && cx >= globals.win_expose.min_x && cy < globals.win_expose.max_y && cy >= globals.win_expose.min_y){
#if COLOR
	if (fg_active != fg_current)
	    do_color_change();
#endif
	XDrawPoint(DISP, currwin.win, globals.gc.rule,
		   cx - currwin.base_x, cy - currwin.base_y);
    }
}

/*
 *	Apply the requested attributes to the last path (box) drawn.
 *	Attributes are reset.
 *	(Not currently implemented.)
 */
static void
do_attribute_path(int last_min_x, int last_max_x, int last_min_y, int last_max_y)
{
    UNUSED(last_min_x);
    UNUSED(last_max_x);
    UNUSED(last_min_y);
    UNUSED(last_max_y);
}

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

    if (sscanf(cp, " %d ", &ps) != 1) {
	XDVI_WARNING((stderr, "invalid .ps command format: %s", cp));
	return;
    }
    pen_size = (2 * ps * resource.pixels_per_inch / currwin.shrinkfactor + 1000) / 2000;
    if (pen_size < 1)
	pen_size = 1;
    else if (pen_size > MAX_PEN_SIZE)
	pen_size = MAX_PEN_SIZE;

#if BUG_888087_FIXED
    if (globals.gc.rule) {
	XGCValues values;
	values.line_width = pen_size;
	XChangeGC(DISP, globals.gc.rule, GCLineWidth, &values);
    }
#endif
}


/*
 *	Print the line defined by previous path commands
 */

static void
flush_path(void)
{
    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(char *cp, Boolean dotted)
{
    int i;
    int numdots;
    int lx0, ly0, lx1, ly1;
    int cx0, cy0, cx1, cy1;
    float inchesperdash;
    double d, spacesize, a, b = 0.0, dx, dy, milliperdash;

    if (sscanf(cp, " %f ", &inchesperdash) != 1) {
	XDVI_WARNING((stderr, "invalid format for dotted/dashed line: %s", cp));
	return;
    }
    if (path_len <= 1 || inchesperdash <= 0.0) {
	XDVI_WARNING((stderr, "invalid 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(char *cp)
{
    int pathx, pathy;

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


/*
 *	Draw to a floating point position
 */

static void
im_fdraw(double x, double y)
{
    if (++path_len >= MAXPOINTS)
	XDVI_FATAL((stderr, "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(int xc, int yc, int xr, int 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(char *cp, Boolean invis)
{
    int xc, yc, xrad, yrad, n;
    float start_angle, end_angle, angle, theta, r;
    double xradius, yradius, xcenter, ycenter;

    n = sscanf(cp, " %d %d %d %d %f %f ", &xc, &yc, &xrad, &yrad,
	       &start_angle, &end_angle);

    if (n != 6) {
	XDVI_WARNING((stderr, "invalid 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);
#if BUG_888087_FIXED
    n = (pen_size * TWOPI) / (theta * currwin.shrinkfactor) + 0.5;
#else
    n = 0.3 * TWOPI / theta + 0.5;
#endif
    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 < start_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(void)
{
    int xp, yp;
    int N;
    int lastx = -1, lasty = -1;
    Boolean lastvalid = False;
    int t1, t2, t3;
    int steps;
    int j;
    int i, w;

    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 (lastvalid)
		line_btw(lastx, lasty, xp, yp);
	    lastx = xp;
	    lasty = yp;
	    lastvalid = True;
	}
    }
    path_len = 0;
}


/*
 *	Shade the last box, circle, or ellipse
 */

static void
shade_last(void)
{
    blacken = whiten = False;
    shade = True;
}


/*
 *	Make the last box, circle, or ellipse, white inside (shade with white)
 */

static void
whiten_last(void)
{
    whiten = True;
    blacken = shade = False;
}


/*
 *	Make last box, etc, black inside
 */

static void
blacken_last(void)
{
    blacken = True;
    whiten = shade = False;
}


/*
 *	Code for PostScript<tm> specials begins here.
 */

#if PS

/*
 *	Information on how to search for PS header and figure files.
 */

static void ps_startup(int, int, const char *);
static void ps_startup2(void);

/* dummy procedures */
static void NullProc(void) { }
static void NullProcInt(int flag) { UNUSED(flag); }
static void NullProcStr(const char *cp) { UNUSED(cp); }


struct psprocs psp = {	/* used for lazy startup of the ps machinery */
    /* toggle */ NullProcInt,
    /* destroy */ NullProc,
    /* interrupt */ NullProc,
    /* endpage */ NullProc,
    /* drawbegin */ ps_startup,
    /* drawraw */ NullProcStr,
    /* drawfile */ NULL,
    /* drawend */ NullProcStr,
    /* beginheader */ ps_startup2,
    /* endheader */ NullProc,
    /* newdoc */ NullProc
};

struct psprocs no_ps_procs = {	/* used if postscript is unavailable */
    /* toggle */ NullProcInt,
    /* destroy */ NullProc,
    /* interrupt */ NullProc,
    /* endpage */ NullProc,
    /* drawbegin */ drawbegin_none,
    /* drawraw */ NullProcStr,
    /* drawfile */ NULL,
    /* drawend */ NullProcStr,
    /* beginheader */ NullProc,
    /* endheader */ NullProc,
    /* newdoc */ NullProc
};

#endif /* PS */

#ifdef MAGICK
int bbox_angle;
Boolean bbox_valid;
unsigned int bbox_width;
unsigned int bbox_height;
int bbox_voffset;
#else
static int bbox_angle;
static Boolean bbox_valid;
static unsigned int bbox_width;
static unsigned int bbox_height;
static int bbox_voffset;
#endif

/* info on bboxes on this page */
struct bbox_info {
    int x;
    int y;
    int w;
    int h;
    int angle;
};
    
static struct bbox_info *g_bbox_info = NULL;
static size_t g_bbox_info_size = 0;
static size_t g_bbox_info_max_size = 0;

/*
  Append the current coordinates to g_bbox_info, unless it already
  contains these coordinates.
*/

static void
append_bbox_info(int x, int y, int w, int h, int angle)
{
    Boolean found = False;
    size_t i;
    const size_t SIZE_STEP = 16;
    
    /* is this box already present? */
    for (i = 0; i < g_bbox_info_size; i++) {
	if (g_bbox_info[i].x == x
	    && g_bbox_info[i].y == y
	    && g_bbox_info[i].w == w
	    && g_bbox_info[i].h == h
	    && g_bbox_info[i].angle == angle) {
	    found = True;
	    break;
	}
    }

    if (!found) {
	g_bbox_info_size++;
	
	while (g_bbox_info_size >= g_bbox_info_max_size) {
	    g_bbox_info_max_size += SIZE_STEP;
	}
	
	g_bbox_info = xrealloc(g_bbox_info, g_bbox_info_max_size * sizeof *g_bbox_info);
	g_bbox_info[g_bbox_info_size - 1].x = x;
	g_bbox_info[g_bbox_info_size - 1].y = y;
	g_bbox_info[g_bbox_info_size - 1].w = w;
	g_bbox_info[g_bbox_info_size - 1].h = h;
	g_bbox_info[g_bbox_info_size - 1].angle = angle;
    }
}

static void
draw_bbox0(int xcorner, int ycorner)
{
    if (bbox_valid) {
#if COLOR
	if (fg_active != fg_current)
	    do_color_change();
#endif

	if (bbox_angle == 0) {
	    ycorner -= bbox_voffset;
	    XDrawRectangle(DISP, currwin.win, globals.gc.high, xcorner, ycorner, bbox_width, bbox_height);
	    if (resource.postscript == 0) {
		if (htex_inside_href) {
		    htex_set_anchorsize(xcorner, ycorner, xcorner + bbox_width, ycorner + bbox_height);
		    htex_set_objecttype(HTEX_IMG);
		}
	    }
	}
	else {
	    float sin_a = sin(bbox_angle * (TWOPI / 360));
	    float cos_a = cos(bbox_angle * (TWOPI / 360));
	    float a, b, c, d;

	    a = cos_a * bbox_width;
	    b = -sin_a * bbox_width;
	    c = -sin_a * bbox_height;
	    d = -cos_a * bbox_height;

	    XDrawLine(DISP, currwin.win, globals.gc.high,
		      xcorner, ycorner,
		      xcorner + (int)rint(a), ycorner + (int)rint(b));
	    XDrawLine(DISP, currwin.win, globals.gc.high,
		      xcorner + (int)rint(a), ycorner + (int)rint(b),
		      xcorner + (int)rint(a + c), ycorner + (int)rint(b + d));
	    XDrawLine(DISP, currwin.win, globals.gc.high,
		      xcorner + (int)rint(a + c), ycorner + (int)rint(b + d),
		      xcorner + (int)rint(c), ycorner + (int)rint(d));
	    XDrawLine(DISP, currwin.win, globals.gc.high,
		      xcorner + (int)rint(c), ycorner + (int)rint(d),
		      xcorner, ycorner);
	}
	bbox_valid = False;
    }
}

/*
  Display the items in bbox_info.
  Invoked from draw_page() in dvi-draw.c.
*/
void
display_bboxes(void)
{
    size_t i;
    
    for (i = 0; i < g_bbox_info_size; i++) {
	if (globals.debug & DBG_PS) {
	    fprintf(stderr, "drawing bbox %lu at %d %d, %d x %d, angle %d\n",
		    (unsigned long)i,
		    g_bbox_info[i].x,
		    g_bbox_info[i].y,
		    g_bbox_info[i].w,
		    g_bbox_info[i].h,
		    g_bbox_info[i].angle);
	}
#if 0
	XDrawRectangle(DISP, currwin.win, globals.gc.high,
		       g_bbox_info[i].x,
		       g_bbox_info[i].y,
		       g_bbox_info[i].w,
		       g_bbox_info[i].h);
#else
	bbox_valid = True;
	bbox_width = g_bbox_info[i].w;
	bbox_height = bbox_voffset = g_bbox_info[i].h;
	bbox_angle = g_bbox_info[i].angle;
	draw_bbox0(g_bbox_info[i].x, g_bbox_info[i].y + bbox_height);
#endif
    }
    bbox_angle = 0;
    bbox_valid = False;
}

void
clear_bboxes(void)
{
    free(g_bbox_info);
    g_bbox_info = NULL;
    g_bbox_info_size = 0;
    g_bbox_info_max_size = 0;
}

void
save_bbox(void)
{
    int xcorner, ycorner;

    if (bbox_valid) {
	xcorner = PXL_H - currwin.base_x;
	ycorner = PXL_V - currwin.base_y;
	
	ycorner -= bbox_voffset;
	append_bbox_info(xcorner, ycorner, bbox_width, bbox_height, bbox_angle);

	/* register boundaries of this box as anchor boundaries */
	if (htex_inside_href) {
	    htex_set_anchorsize(xcorner, ycorner, xcorner + bbox_width, ycorner + bbox_height);
	    htex_set_objecttype(HTEX_IMG);
	}
    }
}


void
draw_bbox(void)
{
    draw_bbox0(PXL_H - currwin.base_x, PXL_V - currwin.base_y);
}

#if PS

static XIOErrorHandler oldhandler;

static int XDviIOErrorHandler(Display * disp)
{
    ps_destroy();
    return oldhandler(disp);
}

static void
actual_startup(int flag)
{
    UNUSED(flag);
    oldhandler = XSetIOErrorHandler(XDviIOErrorHandler);

    /*
     * Figure out what we want to use to display postscript figures
     * and set at most one of the following to True:
     * resource.useGS, resource.useDPS, resource.useNeWS
     *
     * Choose DPS then NEWS then Ghostscript if they are available
     */
    if (!(
#ifdef	PS_DPS
	  (resource.useDPS && initDPS())
#if	defined(PS_NEWS) || defined(PS_GS)
	  ||
#endif
#endif /* PS_DPS */
#ifdef	PS_NEWS
	  (resource.useNeWS && initNeWS())
#ifdef	PS_GS
	  ||
#endif
#endif /* PS_NEWS */
#ifdef	PS_GS
	  (resource.useGS && initGS())
#endif
	  ))
	psp = no_ps_procs;
}

static void
ps_startup(int xul, int yul, const char *cp)
{
    if (resource.postscript == 0) {
	draw_bbox();
	return;
    }
    actual_startup(0);
    psp.drawbegin(xul, yul, cp);
}

static void
ps_startup2(void)
{
    actual_startup(0);
    psp.beginheader();
}


/*
 * dumb parsing of PostScript - search for rotation H. Zeller 1/97
 * Returns true if we find a potentially non-supported command that
 * we want to warn users about.
 */
static Boolean
ps_parseraw(const char *PostScript_cmd)
{
    const char *p;

    bbox_angle = 0;
    p = strstr(PostScript_cmd, "rotate");
    if (p != NULL) {
	while (*p != '\0' && !isdigit((int)*p))
	    --p;
	while (*p != '\0' && isdigit((int)*p))
	    --p;
	if (*p != '+' && *p != '-')
	    ++p;
	sscanf(p, "%d neg rotate", &bbox_angle);
	return True;
    }
    if (strstr(PostScript_cmd, " scale ") != NULL)
	return True;
    return False;
}


void
drawbegin_none(int xul, int yul, const char *cp)
{
    UNUSED(xul);
    UNUSED(yul);
    UNUSED(cp);
    
    draw_bbox();
}


struct tickrec {
    struct tickrec *next;
    int pageno;
    char *command;
    char *tempname;
};

static struct tickrec *tickhead = NULL;	/* head of linked list of */
/* cached information */
static int nticks = 0;	/* number of records total */

#ifndef	TICKCACHESIZE
#define	TICKCACHESIZE	3
#endif

#ifndef	TICKTMP
#define	TICKTMP		"/tmp"
#endif

/*
 *	cachetick() - returns:
 *		NULL		error;
 *		fp == NULL	string was not in cache, fd = file
 *		fp != NULL	string was in cache, fp = cached file
 */

static struct tickrec *
cachetick(const char *filename, kpse_file_format_type pathinfo, FILE **fp, int *fdp)
{
    struct tickrec **linkp;
    struct tickrec *tikp;
    struct tickrec **freerecp;

    linkp = &tickhead;
    freerecp = NULL;
    for (;;) {	/* see if we have it already */
	tikp = *linkp;
	if (tikp == NULL) {	/* if end of list */
	    int fd;

	    if (nticks >= TICKCACHESIZE && freerecp != NULL) {
		tikp = *freerecp;
		*freerecp = tikp->next;
		free(tikp->command);
		unlink(tikp->tempname);
		/* reuse tikp and tikp->tempname */
	    }
	    else {
		tikp = xmalloc(sizeof(struct tickrec));
		tikp->tempname = NULL;
		++nticks;
	    }
	    fd = xdvi_temp_fd(&tikp->tempname);
	    if (fd == -1) {
		perror("Cannot create temporary file");
		free(tikp->tempname);
		free(tikp);
		return NULL;
	    }
	    tikp->command = xstrdup(filename);
	    *fp = NULL;
	    *fdp = fd;
	    break;
	}
	if (strcmp(filename, tikp->command) == 0) {	/* found it */
	    *linkp = tikp->next;	/* unlink it */
	    *fp = XFOPEN(tikp->tempname, OPEN_MODE);
	    if (*fp == NULL) {
		perror(tikp->tempname);
		free(tikp->tempname);
		free(tikp->command);
		free(tikp);
		return NULL;
	    }
	    break;
	}
	if (tikp->pageno != current_page)
	    freerecp = linkp;
	linkp = &tikp->next;
    }
    tikp->next = tickhead;	/* link it in */
    tickhead = tikp;
    tikp->pageno = pathinfo != kpse_tex_ps_header_format ? current_page : -1;
    return tikp;
}

void
ps_clear_cache(void)
{
    struct tickrec *tikp;

    while (tickhead != NULL) {
	tikp = tickhead;
	tickhead = tickhead->next;
	free(tikp->command);
	unlink(tikp->tempname);
	free((char *)tikp->tempname);
	free(tikp);
    }
    nticks = 0;
}

#ifndef	UNCOMPRESS
#define	UNCOMPRESS	"uncompress"
#endif

#ifndef	GUNZIP
#define	GUNZIP		"gunzip"
#endif

#ifndef	BUNZIP2
#define	BUNZIP2		"bunzip2"
#endif

static void send_ps_file(const char *filename, kpse_file_format_type pathinfo);

static void
enable_specials_send_ps_file(XtPointer data)
{
    const char *filename = (const char *)data;
    resource.allow_shell = True;
    redraw_page(); /* to erase the bounding box */
    statusline_info(STATUS_MEDIUM,
		     "Shell specials enabled for this session.",
		     filename);
    send_ps_file(filename, kpse_pict_format);
}

static void
try_open_tempname(int status, struct xchild *this)
{
    if (WIFEXITED(status)) { /* child exited normally */
	if (WEXITSTATUS(status) != 0) {
	    /* default error procedure */
	    handle_child_exit(status, this);
	}
	else {
	    struct tickrec *tikp = (struct tickrec *)this->data;
	    FILE *f = XFOPEN(tikp->tempname, OPEN_MODE);
	    fprintf(stderr, "FILE: %s\n", tikp->tempname);
	    if (f == NULL) {
		perror(tikp->tempname);
	    }
	    else {
		fprintf(stderr, "sending file: %s, %p\n", tikp->tempname, (void *)f);
		/* There's no point in invoking
		   psp.drawfile(tikp->tempname, f);
		   here, since usually it will be already too late (draw_part() which
		   had called us via applicationDoSpecial() will already have terminated).
		   So instead, we just close the file and force a redraw of the entire page.
		*/
		fclose(f);
		globals.ev.flags |= EV_NEWPAGE;
	    }
	}
    }
    else if (WIFSIGNALED(status)) {
	popup_message(globals.widgets.top_level,
		      MSG_WARN,
		      NULL,
		      "Process `%s' terminated abnormally with signal %d.", this->name, WTERMSIG(status));
    }
    else if (WIFSTOPPED(status)) {
	popup_message(globals.widgets.top_level,
		      MSG_WARN,
		      NULL,
		      "Process `%s' stopped by signal %d.", this->name, WSTOPSIG(status));
    }
    else {
	popup_message(globals.widgets.top_level,
		      MSG_WARN,
		      NULL,
		      "Process `%s' terminated with unknown status.", this->name);
    }
}

static void
send_ps_file(const char *filename, kpse_file_format_type pathinfo)
{
    FILE *f;
    int fd;
    static const char *argv[] = { NULL, "-c", NULL, NULL };
    char *bufp = NULL;
    struct tickrec *volatile tikp;
    size_t len;
    char magic1, magic2, magic3;
    static Boolean warned_about_shellescape = False;
    static size_t ffline_len = 0;
    static char *ffline = NULL;
    const size_t FFLINE_STEP = 128;

    if (psp.drawfile == NULL || resource.postscript == 0) {
	return;
    }

    if (filename[0] == '`') {
	if (!resource.allow_shell) {
	    if (!warned_about_shellescape) {
		choice_dialog_sized(globals.widgets.top_level,
				    MSG_INFO,
				    SIZE_MEDIUM,
				    /* helptext */
				    "To enable shell specials, use the \"-allowshell\" command "
				    "line option.\n\n"
				    "WARNING: You should NOT use shell specials like\n"
				    "`gunzip -c file.eps.gz\n"
				    "(e.g. via \\DeclareGraphicsRule{...}) "
				    "to uncompress .eps.gz files, even though some obsolete "
				    "LaTeX documentation might suggest it. Current versions "
				    "of xdvi and dvips will handle .eps.gz files just fine without this trick.\n",
#ifndef MOTIF
				    NULL,
#endif
				    NULL, NULL, /* no pre_callbacks */
				    /* Cancel label/callback */
				    "Cancel", NULL, (XtPointer)NULL,
				    /* Enable label/callback */
				    "Enable", enable_specials_send_ps_file, (XtPointer)filename,
				    /* msg */
				    "This page contains a shell special \"%s\", but execution of shell specials "
				    "is disabled.\n\nYou can now click "
				    "\"Enable\" to enable shell specials for this session only, "
				    "\"Cancel\" to leave the specials disabled, or \"Help\" for more help "
				    "on this topic.\n\n"
				    "Please note that shell specials are a security risk, since they "
				    "allow execution of arbitrary shell commands from the TeX file. "
				    "You should enable them only for DVI files that you created yourself.",
				    filename);
		warned_about_shellescape = True;
	    }
	    else {
		statusline_error(STATUS_MEDIUM,
				 "Info: Shell special \"%s\" disabled.",
				 filename);
	    }
	    draw_bbox();
	    return;
	}

	tikp = cachetick(filename, pathinfo, &f, &fd);
	if (tikp == NULL)	/* if error */
	    return;
	if (f == NULL) {
	    char *argv[4];
	    /* if not cached, need to create.
	       Fork so that we can collect the process' error messages. */

	    /* FIXME: Insecure - this is a /tmp race; shouldn't close(fd), just re-use it */
	    close(fd);

	    len = strlen(filename) + strlen(tikp->tempname) + (4 - 1);
	    if (len > ffline_len) {
		ffline_len += FFLINE_STEP;
		ffline = xrealloc(ffline, ffline_len);
	    }
	    
	    sprintf(ffline, "%s > %s", filename + 1, tikp->tempname);
	    argv[0] = "/bin/sh";
	    argv[1] = "-c";
	    argv[2] = ffline;
	    argv[3] = NULL;
	    fork_process("/bin/sh", False, globals.dvi_file.dirname, try_open_tempname, tikp, argv);
	}
	else {
	    bufp = tikp->tempname;
	    psp.drawfile(bufp, f);
	}
    }
    else {
	char *expanded_filename = NULL;
	struct stat statbuf;
	expanded_filename = find_file(filename, &statbuf, pathinfo);

	if (expanded_filename == NULL &&
	    (pathinfo == kpse_enc_format || pathinfo == kpse_type1_format)) {
	    /* in this case, we also kpathsea-search in the `old' place for
	       backwards compatibility: kpse_tex_ps_header_format (see comment
	       for load_vector(), dvi-draw.c for details) */
	    expanded_filename = kpse_find_file(filename, kpse_tex_ps_header_format, True);
	}
	if (expanded_filename == NULL) {
	    expanded_filename = kpse_find_file(filename, kpse_program_text_format, True);
	}
	if (expanded_filename == NULL) { /* still no success, complain */
	    /* FIXME: this warning may be overwritten by warning about raw PS specials,
	       so additinally dump to stderr. TODO: make statusline printing respect
	       more important messages. */
	    XDVI_WARNING((stderr, "Could not find graphics file \"%s\"", filename));
	    statusline_info(STATUS_MEDIUM, "Warning: Could not find graphics file \"%s\"",
			     filename);
	    draw_bbox();
	    return;
	}
	if (globals.debug & DBG_OPEN)
	    printf("%s:%d: |%s| expanded to |%s|\n", __FILE__, __LINE__, filename, expanded_filename);

	/* may need to adjust ffline_len, ffline */
	while (strlen(expanded_filename) + 1 > ffline_len) {
	    ffline_len += FFLINE_STEP;
	    ffline = xrealloc(ffline, ffline_len);
	}
	strcpy(ffline, expanded_filename);
	bufp = ffline;
	f = XFOPEN(expanded_filename, OPEN_MODE);
	if (f == NULL) {
	    XDVI_WARNING((stderr, "Could not open graphics file \"%s\": %s", expanded_filename, strerror(errno)));
	    statusline_info(STATUS_MEDIUM, "Warning: Could not open graphics file \"%s\": %s",
			     expanded_filename, strerror(errno));
	    free(expanded_filename);
	    draw_bbox();
	    return;
	}
	free(expanded_filename);

	/* check for compressed files */
	len = strlen(filename);
	magic1 = '\037';
	magic3 = '\0';
	if ((len > 2 && strcmp(filename + len - 2, ".Z") == 0
	     && (argv[0] = UNCOMPRESS, magic2 = '\235', True))
	    || (len > 3 && strcmp(filename + len - 3, ".gz") == 0
		&& (argv[0] = GUNZIP, magic2 = '\213', True))
	    || (len > 4 && strcmp(filename + len - 4, ".bz2") == 0
		&& (argv[0] = BUNZIP2, magic1 = 'B', magic2 = 'Z',
		    magic3 = 'h', True))) {
	    if (getc(f) != magic1 || (char)getc(f) != magic2
		|| (magic3 != '\0' && getc(f) != magic3)) {
		rewind(f);
	    }
	    else {
		fclose(f);

		tikp = cachetick(filename, pathinfo, &f, &fd);
		if (tikp == NULL)	/* if error */
		    return;
		if (f == NULL) {	/* if not cached, need to create */
		    pid_t pid;
		    int status;

		    argv[2] = bufp;
		    fflush(stderr);	/* avoid double flushing */
		    pid = vfork();
		    if (pid == 0) {	/* if child */
			(void)dup2(fd, 1);
			(void)execvp(argv[0], (char **)argv);
			XDVI_ERROR((stderr, "Execvp of %s failed: %s", argv[0], strerror(errno)));
			_exit(EXIT_FAILURE);
		    }
		    (void)close(fd);

		    for (;;) {
#if HAVE_WAITPID
			if (waitpid(pid, &status, 0) != -1)
			    break;
#else
# if HAVE_WAIT4
			if (wait4(pid, &status, 0, (struct rusage *)NULL)
			    != -1)
			    break;
# else
			int retval;

			retval = wait(&status);
			if (retval == pid)
			    break;
			if (retval != -1)
			    continue;
# endif	/* HAVE_WAIT4 */
#endif /* HAVE_WAITPID */
			if (errno == EINTR)
			    continue;
			perror("[xdvik] waitpid");
			return;
		    }
		    f = XFOPEN(tikp->tempname, OPEN_MODE);
		    if (f == NULL) {
			perror(tikp->tempname);
			return;
		    }
		}
		bufp = tikp->tempname;
	    }
	}
	/* Success! */
	psp.drawfile(bufp, f); /* this is supposed to close the file */
    }
}


void
ps_destroy(void)
{
    struct tickrec	*tikp;

    /* Note:  old NeXT systems (at least) lack atexit/on_exit.  */
    psp.destroy();
    for (tikp = tickhead; tikp != NULL; tikp = tikp->next)
	if (unlink(tikp->tempname) < 0)
	    perror(tikp->tempname);
}

#endif	/* PS */


void
init_prescan(void)
{
#if PS
    struct tickrec	*tikp;
#endif

    scanned_page = scanned_page_reset = resource.prescan ? -1 : total_pages + 1;
#if PS
    scanned_page_ps = scanned_page_ps_bak = scanned_page;
#if COLOR
    scanned_page_color = scanned_page;
#endif /* COLOR */
#endif /* PS */

    TRACE_FILES((stderr, "init_prescan: scanned_page = %d", scanned_page));
#if PS
    if (resource.postscript == 0)
	scanned_page_ps = total_pages + 1;

    for (tikp = tickhead; tikp != NULL; tikp = tikp->next)
	tikp->pageno = -1;
    psp.newdoc();
#endif

#if COLOR
    if (!resource.use_color)
	scanned_page_color = total_pages + 1;
#endif

    if (ignore_papersize_specials) {
#if PS && COLOR
	scanned_page = scanned_page_ps < scanned_page_color ? scanned_page_ps : scanned_page_color;
#elif PS
	scanned_page = scanned_page_ps;
#elif COLOR
	scanned_page = scanned_page_color;
#else
	scanned_page = total_pages + 1;
#endif
    }
}


static void
psfig_special(char *cp)
{
    char *filename;
    int raww, rawh;

    if (strncmp(cp, ":[begin]", 8) == 0) {
	cp += 8;
	bbox_valid = False;
	bbox_angle = 0;
	if (sscanf(cp, "%d %d\n", &raww, &rawh) >= 2) {
	    bbox_valid = True;
	    bbox_width = pixel_conv(spell_conv(raww));
	    bbox_height = pixel_conv(spell_conv(rawh));
	    bbox_voffset = 0;
	}
	if (INSIDE_MANE_WIN) {
#if	PS
	    psp.drawbegin(PXL_H - currwin.base_x, PXL_V - currwin.base_y, cp);
#else
	    draw_bbox();
#endif
	}
	psfig_begun = True;
    }
    else if (strncmp(cp, " plotfile ", 10) == 0) {
	cp += 10;
	while (isspace((int)*cp))
	    cp++;
	/* handle "`zcat file". Borrowed from dvipsk... */
	if (*cp == '"') {
	    cp++;
	    for (filename = cp; *cp && (*cp != '"'); ++cp);
	}
	else {
	    for (filename = cp; *cp && !isspace((int)*cp); ++cp);
	}
	*cp = '\0';
#if	PS
	if (INSIDE_MANE_WIN)
	    send_ps_file(filename, kpse_pict_format);
#endif
    }
    else if (strncmp(cp, ":[end]", 6) == 0) {
	cp += 6;
#if	PS
	if (INSIDE_MANE_WIN) {
	    psp.drawend(cp);
	}
#endif
	bbox_valid = False;
	psfig_begun = False;
    }
    else if (*cp == ':') {
	/* I am going to send some raw postscript stuff */
	++cp;	/* skip the colon */
#if	PS
	if (ps_parseraw(cp))
	    have_raw_postscript = True;
	if (INSIDE_MANE_WIN)
	    psp.drawraw(cp);
#endif
    }
    else { /* attempt to parse pstricks color specials */
#if COLOR
	struct rgb color;
	if (parse_color(cp, cp, &color, True)) {
	    /* clear stack */
	    if (rcs_head == NULL) {
		rcs_head = xmalloc(sizeof *rcs_head);
		rcs_head->prev = rcs_head->next = NULL;
	    }
	    rcs_top = rcs_head;
	    color_bot_size = 0;
	    
	    /* Change top of stack */
	    rcs_top->color = color;
	    
	    set_fg_color(&color);
	}
#endif	    
	/* also raw PostScript, but no extra colon to skip */
#if PS
	if (INSIDE_MANE_WIN) {
	    if (ps_parseraw(cp))
		have_raw_postscript = True;
	    
	    if (psfig_begun)
		psp.drawraw(cp);
	    else {
		psp.drawbegin(PXL_H - currwin.base_x,
			      PXL_V - currwin.base_y, cp);
		psp.drawend("");
	    }
	}
#endif
    }
}


/*	Keys for epsf specials */

static const char *keytab[] = { "clip",
				"llx",
				"lly",
				"urx",
				"ury",
				"rwi",
				"rhi",
				"hsize",
				"vsize",
				"hoffset",
				"voffset",
				"hscale",
				"vscale",
				"angle"
};

#define	KEY_LLX	keyval[0]
#define	KEY_LLY	keyval[1]
#define	KEY_URX	keyval[2]
#define	KEY_URY	keyval[3]
#define	KEY_RWI	keyval[4]
#define	KEY_RHI	keyval[5]

#define	NKEYS	(sizeof keytab /sizeof *keytab)
#define	N_ARGLESS_KEYS 1

static void
epsf_special(char *cp)
{
    char *filename;
    static char *buffer;
    static unsigned int buflen = 0;
    unsigned int len;
    char *q;
    int flags = 0;
    double keyval[6] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

    filename = cp;
    if (*cp == '\'' || *cp == '"') {
	do
	    ++cp;
	while (*cp != '\0' && *cp != *filename);
	++filename;
    }
    else
	while (*cp != '\0' && !isspace((int)*cp))
	    ++cp;
    if (*cp != '\0')
	*cp++ = '\0';
    while (isspace((int)*cp))
	++cp;
    len = strlen(cp) + NKEYS + 30;
    if (buflen < len) {
	if (buflen != 0)
	    free(buffer);
	buflen = len;
	buffer = xmalloc(buflen);
    }
    strcpy(buffer, "@beginspecial");
    q = buffer + strlen(buffer);
    while (*cp != '\0') {
	char *p1 = cp;
	size_t keyno;

	while (*p1 != '=' && !isspace((int)*p1) && *p1 != '\0')
	    ++p1;
	for (keyno = 0;; ++keyno) {
	    if (keyno >= NKEYS) {
		if (globals.warn_spec_now)
		    XDVI_WARNING((stderr, "Ignoring unknown keyword (%.*s) in \\special",
				  (int)(p1 - cp), cp));
		break;
	    }
	    if (memcmp(cp, keytab[keyno], p1 - cp) == 0) {
		if (keyno >= N_ARGLESS_KEYS) {
		    while (isspace((int)*p1))
			++p1;
		    if (*p1 == '=') {
			++p1;
			while (isspace((int)*p1))
			    ++p1;
		    }
		    if (keyno < N_ARGLESS_KEYS + 6) {
			keyval[keyno - N_ARGLESS_KEYS] = atof(p1);
			flags |= (1 << (keyno - N_ARGLESS_KEYS));
		    }
		    *q++ = ' ';
		    while (!isspace((int)*p1) && *p1 != '\0')
			*q++ = *p1++;
		}
		*q++ = ' ';
		*q++ = '@';
		strcpy(q, keytab[keyno]);
		q += strlen(q);
		break;
	    }
	}
	cp = p1;
	while (!isspace((int)*cp) && *cp != '\0')
	    ++cp;
	while (isspace((int)*cp))
	    ++cp;
    }
    strcpy(q, " @setspecial\n");

    bbox_valid = False;
    /* Validate the case where both rwi and rhi are undefined
     * (and llx, lly, urx, ury are properly defined) */
    if (!(flags & 0x30) && (flags & 0xf) == 0xf) {
	KEY_RWI = (KEY_URX - KEY_LLX) * 10;
	flags |= 0x10;
    }
    if ((flags & 0x30) == 0x30 || ((flags & 0x30) && (flags & 0xf) == 0xf)) {
	bbox_valid = True;
	bbox_width = 0.1 * ((flags & 0x10) ? KEY_RWI
			    : KEY_RHI * (KEY_URX - KEY_LLX) / (KEY_URY -
							       KEY_LLY)) *
	    dimconv / currwin.shrinkfactor + 0.5;
	bbox_voffset = bbox_height =
	    0.1 * ((flags & 0x20) ? KEY_RHI : KEY_RWI * (KEY_URY - KEY_LLY) /
		   (KEY_URX - KEY_LLX))
	    * dimconv / currwin.shrinkfactor + 0.5;
    }

    if (INSIDE_MANE_WIN) {
#if	PS
	psp.drawbegin(PXL_H - currwin.base_x, PXL_V - currwin.base_y, buffer);
	/* talk directly with the DPSHandler here */
	send_ps_file(filename, kpse_pict_format);
	psp.drawend(" @endspecial");
#else
	draw_bbox();
#endif
    }
    bbox_valid = False;
}


static void
quote_special(char *cp)
{
    bbox_valid = False;

#if	PS
    if (currwin.win == mane.win) {
	psp.drawbegin(PXL_H - currwin.base_x, PXL_V - currwin.base_y,
		      "@beginspecial @setspecial ");
	/* talk directly with the DPSHandler here */
	psp.drawraw(cp + 1);
	psp.drawend(" @endspecial");
    }
#endif

    /* nothing else to do--there's no bbox here */
}

#if	PS

static void
scan_header(char *cp)
{
    char *filename;
    /* default for .pro files: */
    kpse_file_format_type our_format;
    
#if PS_GS
    if (gs_postpone_prescan)
	return;
#endif
    filename = cp;
    if (*cp == '\'' || *cp == '"') {
	do
	    ++cp;
	while (*cp != '\0' && *cp != *filename);
	*cp = '\0';
	++filename;
    }

    psp.beginheader();
    /* check for suffixes other than `.pro' (default PS headers),
       case-insensitive */
    if (str_is_suffix(".pfa", filename, False)
	|| str_is_suffix(".pfb", filename, False))
	our_format = kpse_type1_format;
    else if (str_is_suffix(".enc", filename, False))
	our_format = kpse_enc_format;
    else
	our_format = kpse_tex_ps_header_format;
    
    send_ps_file(filename, our_format);
}

static void
scan_bang(char *cp)
{
    psp.beginheader();
    psp.drawraw(cp + 1);
}

#endif /* PS */

#if COLOR

/*
 *	Table of dvips color names.  Produced by passing the table in
 *	dvipsk/color.lpro through the following perl script, and then
 *	through sort.
 *
 *	#! /usr/bin/perl -w
 *
 *	sub cvpart {
 *	    return $_[0] < 1.0 ? 1.0 - $_[0] : 0.0;
 *	}
 *	
 *	$pat = "^\\/(\\w+)\\s*\\{\\s*([0-9.]+)\\s*([0-9.]+)\\s*([0-9.]+)"
 *	  . "\\s*([0-9.]+)\\s*setcmykcolor\\s*\\}\\s*DC\\s*\$";
 *	while (<STDIN>) {
 *	    chop;
 *	    if (/^%%/) {next;}
 *	    if (/$pat/o) {
 *		printf "\t\tDVIPSCOLORDESC(%2d, \"%s\", %g, %g, %g),\n",
 *		  length($1), $1,
 *		  cvpart($2 + $5), cvpart($3 + $5), cvpart($4 + $5);
 *	    }
 *	    else {
 *		print "Bad line: ", $_, "\n";
 *	    }
 *	}
 */

struct dvipscolor {
    const char *name;
    struct rgb color;
};

static Boolean
parse_color(const char *cp0, const char *cp,
	    struct rgb *rgbp, Boolean generic_ps_flag)
{
    double r, g, b;
    double k;
    double hue, sat, bri;
    char dummy[8];

#define	CVPART(x)	((int)((x) * 65535 + 0.5))
#define	COLOR_DESC(name, r, g, b) \
		{name, {CVPART(r), CVPART(g), CVPART(b)}}

    /* hash table to speed up lookup in following list of color names */
    static hashTableT colornames_hash;
    /*
      Use a prime close to sizof colornames / sizeof colornames[0].
      You might want to update this when extending the colornames array,
      and to check the filling factor and average chain length by uncommenting the
      hash_print(colornames_hash, True);
      below. I usually go for filling > 50% and avg. chain len < 2.
      
      The 68-elem array below results in:
      79 buckets, 43 nonempty (54%); 68 entries, average chain 1.6.
    */
    static const size_t colornames_hash_size = 79;
    
    static struct dvipscolor colornames[] = {
	COLOR_DESC("Red", 1, 0, 0),
	COLOR_DESC("Tan", 0.86, 0.58, 0.44),
	COLOR_DESC("Blue", 0, 0, 1),
	COLOR_DESC("Cyan", 0, 1, 1),
	COLOR_DESC("Gray", 0.5, 0.5, 0.5),
	COLOR_DESC("Plum", 0.5, 0, 1),
	COLOR_DESC("Black", 0, 0, 0),
	COLOR_DESC("Brown", 0.4, 0, 0),
	COLOR_DESC("Green", 0, 1, 0),
	COLOR_DESC("Melon", 1, 0.54, 0.5),
	COLOR_DESC("Peach", 1, 0.5, 0.3),
	COLOR_DESC("Sepia", 0.3, 0, 0),
	COLOR_DESC("White", 1, 1, 1),
	COLOR_DESC("Maroon", 0.68, 0, 0),
	COLOR_DESC("Orange", 1, 0.39, 0.13),
	COLOR_DESC("Orchid", 0.68, 0.36, 1),
	COLOR_DESC("Purple", 0.55, 0.14, 1),
	COLOR_DESC("Salmon", 1, 0.47, 0.62),
	COLOR_DESC("Violet", 0.21, 0.12, 1),
	COLOR_DESC("Yellow", 1, 1, 0),
	COLOR_DESC("Apricot", 1, 0.68, 0.48),
	COLOR_DESC("Emerald", 0, 1, 0.5),
	COLOR_DESC("Fuchsia", 0.45, 0.01, 0.92),
	COLOR_DESC("Magenta", 1, 0, 1),
	COLOR_DESC("SkyBlue", 0.38, 1, 0.88),
	COLOR_DESC("Thistle", 0.88, 0.41, 1),
	COLOR_DESC("BrickRed", 0.72, 0, 0),
	COLOR_DESC("Cerulean", 0.06, 0.89, 1),
	COLOR_DESC("Lavender", 1, 0.52, 1),
	COLOR_DESC("Mahogany", 0.65, 0, 0),
	COLOR_DESC("Mulberry", 0.64, 0.08, 0.98),
	COLOR_DESC("NavyBlue", 0.06, 0.46, 1),
	COLOR_DESC("SeaGreen", 0.31, 1, 0.5),
	COLOR_DESC("TealBlue", 0.12, 0.98, 0.64),
	COLOR_DESC("BlueGreen", 0.15, 1, 0.67),
	COLOR_DESC("CadetBlue", 0.38, 0.43, 0.77),
	COLOR_DESC("Dandelion", 1, 0.71, 0.16),
	COLOR_DESC("Goldenrod", 1, 0.9, 0.16),
	COLOR_DESC("LimeGreen", 0.5, 1, 0),
	COLOR_DESC("OrangeRed", 1, 0, 0.5),
	COLOR_DESC("PineGreen", 0, 0.75, 0.16),
	COLOR_DESC("RawSienna", 0.55, 0, 0),
	COLOR_DESC("RedOrange", 1, 0.23, 0.13),
	COLOR_DESC("RedViolet", 0.59, 0, 0.66),
	COLOR_DESC("Rhodamine", 1, 0.18, 1),
	COLOR_DESC("RoyalBlue", 0, 0.5, 1),
	COLOR_DESC("RubineRed", 1, 0, 0.87),
	COLOR_DESC("Turquoise", 0.15, 1, 0.8),
	COLOR_DESC("VioletRed", 1, 0.19, 1),
	COLOR_DESC("Aquamarine", 0.18, 1, 0.7),
	COLOR_DESC("BlueViolet", 0.1, 0.05, 0.96),
	COLOR_DESC("DarkOrchid", 0.6, 0.2, 0.8),
	COLOR_DESC("OliveGreen", 0, 0.6, 0),
	COLOR_DESC("Periwinkle", 0.43, 0.45, 1),
	COLOR_DESC("Bittersweet", 0.76, 0.01, 0),
	COLOR_DESC("BurntOrange", 1, 0.49, 0),
	COLOR_DESC("ForestGreen", 0, 0.88, 0),
	COLOR_DESC("GreenYellow", 0.85, 1, 0.31),
	COLOR_DESC("JungleGreen", 0.01, 1, 0.48),
	COLOR_DESC("ProcessBlue", 0.04, 1, 1),
	COLOR_DESC("RoyalPurple", 0.25, 0.1, 1),
	COLOR_DESC("SpringGreen", 0.74, 1, 0.24),
	COLOR_DESC("YellowGreen", 0.56, 1, 0.26),
	COLOR_DESC("MidnightBlue", 0, 0.44, 0.57),
	COLOR_DESC("YellowOrange", 1, 0.58, 0),
	COLOR_DESC("CarnationPink", 1, 0.37, 1),
	COLOR_DESC("CornflowerBlue", 0.35, 0.87, 1),
	COLOR_DESC("WildStrawberry", 1, 0.04, 0.61),
    };
#undef CVPART
#undef COLOR_DESC

    UNUSED(cp0);
    
    while (*cp == ' ') ++cp;

    if (generic_ps_flag) {
        /*
	  check for pstricks color specials.
	  The dummy buffer is to ensure that there are no other (general PS)
	  commands following (we wouldn't know how to deal with these).
	*/
	if (sscanf(cp, "%lf %lf %lf setrgbcolor %7s", &r, &g, &b, dummy) == 3
	    && r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1) {
	    rgbp->r = r * 65535 + 0.5;
	    rgbp->g = g * 65535 + 0.5;
	    rgbp->b = b * 65535 + 0.5;
	    return True;
	}
	else if (sscanf(cp, "%lf %lf %lf %lf setcmykcolor %7s", &r, &g, &b, &k, dummy) == 4
		 && r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1
		 && k >= 0 && k <= 1) {
	    r = 1.0 - r - k;	/* cyan --> red */
	    rgbp->r = (r < 0 ? 0 : r * 65535 + 0.5);
	    g = 1.0 - g - k;	/* magenta --> green */
	    rgbp->g = (g < 0 ? 0 : g * 65535 + 0.5);
	    b = 1.0 - b - k;	/* yellow --> blue */
	    rgbp->b = (b < 0 ? 0 : b * 65535 + 0.5);
	    return True;
	}
	else if (sscanf(cp, "%lf %lf %lf sethsbcolor %7s", &hue, &sat, &bri, dummy) == 3 && hue >= 0
		 && hue <= 1 && sat >= 0 && sat <= 1 && bri >= 0 && bri <= 1) {
	    int	h	= (int) (6 * hue);
	    double	p	= bri * (1 - sat);
	    double	q	= bri * (1 - sat * (6 * hue - h));
	    double	t	= p - q + bri;
	    
	    switch (h) {
	    case 0:  r = bri; g = t; b = p; break; /* Red - Yel */
	    case 1:  r = q; g = bri; b = p; break; /* Yel - Grn */
	    case 2:  r = p; g = bri; b = t; break; /* Grn - Cyn */
	    case 3:  r = p; g = q; b = bri; break; /* Cyn - Blu */
	    case 4:  r = t; g = p; b = bri; break; /* Blu - Mag */
	    case 5:  r = bri; g = p; b = q; break; /* Mag - Red */
	    case 6:  r = bri; g = b = p;    break; /* Red */
	    }
	    rgbp->r = r * 65535 + 0.5;
	    rgbp->g = g * 65535 + 0.5;
	    rgbp->b = b * 65535 + 0.5;
	    return True;
	}
	else if (sscanf(cp, "%lf setgray %7s", &r, dummy) == 1 && r >= 0 && r <= 1) {
	    rgbp->r = rgbp->g = rgbp->b = r * 65535 + 0.5;
	    return True;
	}
	else
	    return False;
    }

    /* no generic PS command; must be a dvips color special */
    
    if (memicmp(cp, "rgb ", 4) == 0) {
	if (sscanf(cp + 3, "%lf %lf %lf", &r, &g, &b) == 3
	    && r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1) {
	    rgbp->r = r * 65535 + 0.5;
	    rgbp->g = g * 65535 + 0.5;
	    rgbp->b = b * 65535 + 0.5;
	    return True;
	}
    }
    else if (memicmp(cp, "gray ", 5) == 0) {
	if (sscanf(cp + 4, "%lf", &r) == 1 && r >= 0 && r <= 1) {
	    rgbp->r = rgbp->g = rgbp->b = r * 65535 + 0.5;
	    return True;
	}
    }
    else if (memicmp(cp, "cmyk ", 5) == 0) {
	if (sscanf(cp + 4, "%lf %lf %lf %lf", &r, &g, &b, &k) == 4
	    && r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1
	    && k >= 0 && k <= 1) {
	    r = 1.0 - r - k;	/* cyan --> red */
	    rgbp->r = (r < 0 ? 0 : r * 65535 + 0.5);
	    g = 1.0 - g - k;	/* magenta --> green */
	    rgbp->g = (g < 0 ? 0 : g * 65535 + 0.5);
	    b = 1.0 - b - k;	/* yellow --> blue */
	    rgbp->b = (b < 0 ? 0 : b * 65535 + 0.5);
	    return True;
	}
    }
    else if (memicmp(cp, "hsb ", 4) == 0) {
	if (sscanf(cp + 3, "%lf %lf %lf", &hue, &sat, &bri) == 3 && hue >= 0
	    && hue <= 1 && sat >= 0 && sat <= 1 && bri >= 0 && bri <= 1) {
	    int	h	= (int) (6 * hue);
	    double	p	= bri * (1 - sat);
	    double	q	= bri * (1 - sat * (6 * hue - h));
	    double	t	= p - q + bri;

	    switch (h) {
	    case 0:  r = bri; g = t; b = p; break; /* Red - Yel */
	    case 1:  r = q; g = bri; b = p; break; /* Yel - Grn */
	    case 2:  r = p; g = bri; b = t; break; /* Grn - Cyn */
	    case 3:  r = p; g = q; b = bri; break; /* Cyn - Blu */
	    case 4:  r = t; g = p; b = bri; break; /* Blu - Mag */
	    case 5:  r = bri; g = p; b = q; break; /* Mag - Red */
	    case 6:  r = bri; g = b = p;    break; /* Red */
	    }
	    rgbp->r = r * 65535 + 0.5;
	    rgbp->g = g * 65535 + 0.5;
	    rgbp->b = b * 65535 + 0.5;
	    return True;
	}
    }
    else { /* is it a dvips color name? */
	size_t idx;
	size_t len = 0;
	static char *testname = NULL;
	static size_t testname_len = 0;
	size_t testname_step = 16;
	
	if (colornames_hash.size == 0) { /* initialize hash table */
	    size_t i;
	    colornames_hash = hash_create(colornames_hash_size);
	    /* insert color names and array indexes */
	    for (i = 0; i < (sizeof colornames / sizeof colornames[0]); i++) {
		put_str_int_hash(&colornames_hash, colornames[i].name, i);
	    }
	}
	/* uncomment the following to get statistics on hashing: */
	/* hash_print(colornames_hash, True); */

	/* need to copy to a null-terminated string for hash lookup ... */
	while (isalpha((int)cp[len])) {
	    ++len;	/* get length of color name */
	}
	while (len >= testname_len) {
	    testname_len += testname_step;
	    testname = xrealloc(testname, testname_len);
	}
	memcpy(testname, cp, len);
	testname[len] = '\0';
	
	if (find_str_int_hash(&colornames_hash, testname, &idx)) {
	    *rgbp = colornames[idx].color;
	    return True;
	}
    }

    /* not found */
    XDVI_WARNING((stderr, "Ignoring invalid color name in special `%s'", cp0 == NULL ? "<NULL>" : cp0));
    return False;
}

void
init_page_colors(struct rgb *foreground, struct rgb *background)
{
    int	i;
    
    page_colors.size = total_pages;
    page_colors.stack = xmalloc(page_colors.size * sizeof *page_colors.stack);

    for (i = 0; i <= scanned_page + 1; ++i) {
	page_colors.stack[i].bg = *background;
	page_colors.stack[i].colorstack = foreground;
	page_colors.stack[i].stacksize = 1;
    }
    for (; i < total_pages; i++) {
	page_colors.stack[i].colorstack = NULL;
    }

    scanstack_head.color = *foreground;
    scanstack_len = 1;			/* nothing yet */
    scanstack_current = &scanstack_head;	/* stack position */
}


static void
scan_bg_color(const char *cp)
{
    if (!resource.use_color)
	return;

    if (page_colors.stack == NULL)
	init_page_colors(&fg_initial, &bg_initial);

    ASSERT(scanned_page < (int)page_colors.size, "page_colors.size too small");
    (void) parse_color(cp, cp + 11, &(page_colors.stack[scanned_page + 1].bg), False);
}

void
scan_color(const char *cp)
{
    const char	*cp1 = cp + 6;

    if (!resource.use_color)
	return;

    while (*cp1 == ' ') ++cp1;

    if (page_colors.stack == NULL)
	init_page_colors(&fg_initial, &bg_initial);

    if (memicmp(cp1, "push ", 5) == 0) {
	if (scanstack_current->next == NULL) {	/* if at end */
	    scanstack_current->next = xmalloc(sizeof *scanstack_current);
	    scanstack_current->next->prev = scanstack_current;
	    scanstack_current->next->next = NULL;
	}
	scanstack_current = scanstack_current->next;
	++scanstack_len;
	if (!parse_color(cp, cp1 + 5, &scanstack_current->color, False))
	    scanstack_current->color = scanstack_current->prev->color;
    }
    else if (memicmp(cp1, "pop", 3) == 0) {
	if (scanstack_len <= 1) {
	    XDVI_WARNING((stderr, "Color pop occurred with empty color stack."));
	}
	else {
	    scanstack_current = scanstack_current->prev;
	    --scanstack_len;
	}
    }
    else {
	(void) parse_color(cp, cp1, &scanstack_head.color, False);
	if (scanstack_len > 1) {
	    struct colorframe	*cfp;

	    XDVI_WARNING((stderr, "Global color change occurred with non-empty color stack!\n"
			  "Trying to recover by setting all stack entries to that color."));
	    for (cfp = scanstack_head.next;; cfp = cfp->next) {
		cfp->color = scanstack_head.color;
		if (cfp == scanstack_current) break;
	    }
	}
    }
}

void
scan_color_eop(void)
{
    int i;
    struct rgb *prev, *p1, *rgbp;
    struct colorframe *cf;

    if (page_colors.stack == NULL)
	return;

    /* set background color for next page */
    if (scanned_page + 1 < total_pages) {
	ASSERT(scanned_page + 1 < (int)page_colors.size, "page_colors.size too small");
	page_colors.stack[scanned_page + 1].bg = page_colors.stack[scanned_page].bg;
    }
    ASSERT(scanned_page < (int)page_colors.size, "page_colors.size too small");
    /* save the stack contents */
    page_colors.stack[scanned_page].stacksize = scanstack_len;

    prev = &fg_initial;
    i = 1;
    if (scanned_page > 0) {
	prev = page_colors.stack[scanned_page - 1].colorstack;
	i = page_colors.stack[scanned_page - 1].stacksize;
    }
    if (scanstack_len <= i) {
	/* try to reuse the previous array */
	p1 = prev;
	cf = &scanstack_head;
	for (i = 0;;) {
	    if (p1->r != cf->color.r || p1->g != cf->color.g || p1->b != cf->color.b)
		break;
	    if (++i >= scanstack_len) {	/* end loop; reuse memory */
		page_colors.stack[scanned_page].colorstack = prev;
		return;	/* done */
	    }
	    ++p1;
	    cf = cf->next;
	}
    }
    page_colors.stack[scanned_page].colorstack = rgbp = xmalloc(scanstack_len * sizeof *rgbp);
    cf = &scanstack_head;
    for (i = 0; i < scanstack_len; ++i) {
	*rgbp++ = cf->color;
	cf = cf->next;
    }
}


/*
 *	We don't actually do any X calls yet to change colors; that can wait
 *	until a character or rule needs to be drawn.
 */

void
set_fg_color(const struct rgb *color)
{
    struct fgrec	**fgpp;

    if (fg_current != NULL
	&& color->r == fg_current->color.r
	&& color->g == fg_current->color.g
	&& color->b == fg_current->color.b) {
	return;
    }

    ASSERT(bg_current != NULL, "Background color not initialized");
    for (fgpp = &bg_current->fg_head;;) {
	fg_current = *fgpp;
	if (fg_current == NULL) {	/* if color is not in list */
	    fg_current = *fgpp = xmalloc(sizeof *fg_current);
	    fg_current->next = NULL;
	    fg_current->color = *color;
	    fg_current->pixel_good = False;
#if GREY
	    fg_current->palette_good = False;
#endif
	    break;
	}
	if (fg_current->color.r == color->r
	    && fg_current->color.g == color->g
	    && fg_current->color.b == color->b)
	    break;
	fgpp = &fg_current->next;
    }
    if (globals.debug & DBG_DVI)
	printf("Changing fg color to %5d %5d %5d\n",
	       color->r, color->g, color->b);
}

#if 0
static void
show_stack(void) {
    struct colorframe *ptr;
    int i;
    fprintf(stderr, "FG: %d,%d,%d\n", fg_current->color.r, fg_current->color.b, fg_current->color.g);
    for (i = 0, ptr = rcs_top; ptr != NULL; ptr = ptr->prev, i++) {
	fprintf(stderr, "stack %d: %d,%d,%d\n", i, ptr->color.r, ptr->color.b, ptr->color.g);
    }
}
#endif

void
color_special(const char *cp)
{
    while (*cp == ' ') ++cp;

    if (memicmp(cp, "push ", 5) == 0) {
	if (rcs_top == NULL) {
	    if (rcs_head == NULL) {
		rcs_head = xmalloc(sizeof *rcs_head);
		rcs_head->prev = rcs_head->next = NULL;
	    }
	    rcs_top = rcs_head;
	}
	else {
	    struct colorframe *rcs_next;

	    rcs_next = rcs_top->next;
	    if (rcs_next == NULL) {
		rcs_next = rcs_top->next = xmalloc(sizeof *rcs_next);
		rcs_next->prev = rcs_top;
		rcs_next->next = NULL;
	    }
	    rcs_top = rcs_next;
	}
	if (!parse_color(NULL, cp + 5, &rcs_top->color, False)) {
	    if (rcs_top->prev != NULL)
		rcs_top->color = rcs_top->prev->color;
	    else
		rcs_top->color = color_bottom[color_bot_size - 1];
	}
	set_fg_color(&rcs_top->color);
    }
    else if (memicmp(cp, "pop", 3) == 0) {
	/* Pop stack */
	if (rcs_top != NULL) {
	    if (color_bot_size > 0)
		rcs_top = rcs_top->prev;
	    else
		return;
	}
	else if (color_bot_size > 1)
	    --color_bot_size;
	else {
	    if (scanned_page_reset >= 0) {
		/* turn on scanning and redraw the page */
		scanned_page = scanned_page_color = scanned_page_reset = -1;
#if PS
		scanned_page_ps = scanned_page;
#endif
		/* 		fprintf(stderr, "forcing redraw!!!\n"); */
		globals.ev.flags |= EV_NEWPAGE;		/* force a redraw */
		longjmp(globals.ev.canit, 1);
	    }
	    return;
	}
	/* 	fprintf(stderr, "bot_size: %d\n", color_bot_size); */
	set_fg_color(rcs_top != NULL ? &rcs_top->color
		     : &color_bottom[color_bot_size - 1]);
    }
    else {
	struct rgb color;

	if (!parse_color(NULL, cp, &color, False))
	    return;

	/* clear stack */
	if (rcs_head == NULL) {
	    rcs_head = xmalloc(sizeof *rcs_head);
	    rcs_head->prev = rcs_head->next = NULL;
	}
	rcs_top = rcs_head;
	color_bot_size = 0;

	/* Change top of stack */
	rcs_top->color = color;

	set_fg_color(&color);
    }
}

#endif /* COLOR */

static unsigned int
myatopix(const char **pp)
{
#define SCR_LEN 16
    unsigned int value;
    const char *cp = *pp;
    char scr[16];
    const char *p0, *p1;

    p0 = cp;
    while ((*cp >= '0' && *cp <= '9') || *cp == '.')
	++cp;
    p1 = cp;
    while (isspace((int)*cp))
	++cp;
    if (*cp >= 'a' && *cp <= 'z' && cp[1] >= 'a' && cp[1] <= 'z') {
	/* if units are present */
	if (p1 - p0 <= SCR_LEN - 3) {
	    sprintf(scr, "%.*s%c%c", (int)(p1 - p0), p0, *cp, cp[1]);
	    value = atopix(scr, False);
	}
	else
	    value = 0;
	cp += 2;
    }
    else
	value = atopix(p0, False);

    *pp = cp;
    return value;
#undef SCR_LEN
}

static void
scan_papersize(const char *cp0)
{
    const char *cp = cp0;
    unsigned int w, h;
    double mag = 1.0;

    if (ignore_papersize_specials)
	return;

    if (*cp == '*') {
	do
	    ++cp;
	while (isspace((int)*cp));
	mag = magnification * .001;
    }
	    
    w = myatopix(&cp) * mag + 0.5;

    while (isspace((int)*cp))
	++cp;
    if (*cp == ',')
	do
	    ++cp;
	while (isspace((int)*cp));

    h = myatopix(&cp) * mag + 0.5;

    if (w == 0 || h == 0)
	XDVI_WARNING((stderr, "Invalid papersize special `%s'", cp0));
    else {
	/* we have a paper size special; disable xdvirc_geometry:
	   fprintf(stderr, "---------- scanned papersize: %dx%d\n", w, h);
	   resource.xdvirc_geometry = NULL;
	*/
	pageinfo_set_page_width(scanned_page + 1, w);
	pageinfo_set_window_width(scanned_page + 1, w);
	pageinfo_set_page_height(scanned_page + 1, h);
	pageinfo_set_window_height(scanned_page + 1, h);
    }
}

/*
 *	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.
 */

static char *
endofcommand(char *cp)
{
    while (isspace((int)*cp))
	++cp;
    if (*cp != '=')
	return NULL;
    do
	++cp;
    while (isspace((int)*cp));
    return cp;
}

#define	CMD(x, y)	((x) << 8 | (y))

void
applicationDoSpecial(char *cp, size_t len)
{
    char *p;

    if (globals.debug & DBG_DVI)
	printf("          `%s'\n", cp);

    while (isspace((int)*cp))
	++cp;

    /* Ignore initial "xdvi:" */
    if (memcmp(cp, "xdvi:", 5) == 0) {
	cp += 5;
	while (isspace((int)*cp))
	    ++cp;
    }
	
    /* PostScript specials */
#ifdef MAGICK
    if (resource.useMAGICK) {
	if (Magick_parse_special(cp))
	    return;
    }
#endif

    if (*cp == '"') {
	quote_special(cp);
	return;
    }
    if (memicmp(cp, "ps:", 3) == 0) {
	cp += 3;
	psfig_special(cp);
	/* check for hdvips hyperlinks */
	if (memicmp(cp, "sdict begin ", strlen("sdict begin ")) == 0) {
	    static Boolean warned_hypertex_too_old = False;
	    char *match = NULL;
	    if (warned_hypertex_too_old) /* don't continue evaluating links in this case */
		return;
	    cp += strlen("sdict begin ");
	    if (memcmp(cp, "H.S", 3) == 0
		|| memcmp(cp, "H.R", 3) == 0
		|| memcmp(cp, "H.B", 3) == 0
		/* following 2 conditions could be more restrictive: between `begin' and H.A/H.L,
		   there should be a single number (baselineskip in pt) */
		|| (match = strstr(cp, "H.A")) != NULL
		|| (match = strstr(cp, "H.L")) != NULL
		|| (match = strstr(cp, "/Action")) != NULL
		|| (match = strstr(cp, "/Link")) != NULL
		|| (match = strstr(cp, "/View")) != NULL) {
		if (match != NULL)
		    htex_do_special(match, len - 3 - (match - cp) - strlen("sdict begin "));
		else
		    htex_do_special(cp, len - strlen("sdict begin "));
	    }
	    else if (!warned_hypertex_too_old && strstr(cp, "HyperStart") != NULL) {
		popup_message(globals.widgets.top_level,
			      MSG_WARN, NULL,
			      "This DVI was created with a too old version of the `dvips' hyperref driver - "
			      "disabling hyperlinks.\n"
			      "To fix this, you should either upgrade to a current version of hyperref "
			      "(see http://www.tug.org/applications/hyperref/), "
			      "or use the `hypertex' package option, like this:\n\\usepackage[hypertex]{hyperref}\n"
			      "(Be aware though that this option won't work for PS->PDF conversion!)");
		warned_hypertex_too_old = True;
	    }
	}
	else {
	    /* When not ignoring SDict entries, the distiller and pagecolor
	       code in lshort.dvi from CTAN:info/lshort/russian/lshrtdvi.zip
	       causes a failed assertion for 'color_bot_size > 0' in dvi-draw.c;
	       there's something wrong with the parsing order/event handling here
	       (see bug #856547).
	       But we also don't want those entries to trigger erasepage_gs(), so
	       it's correct to ignore them here.
	    */
#if PS_GS && GS_PIXMAP_CLEARING_HACK
	    had_ps_specials = True;
#endif
	}
	return;
    }
    if (memicmp(cp, "psfile", 6) == 0 && (p = endofcommand(cp + 6)) != NULL) {
    	epsf_special(p);
#if PS_GS && GS_PIXMAP_CLEARING_HACK
	had_ps_specials = True;
#endif
	return;
    }
    if (memicmp(cp, "html:", 5) == 0) {
	htex_do_special(cp + 5, len - 5);
	return;
    }

#if COLOR
    if (memicmp(cp, "color ", 6) == 0) {
	/* 	fprintf(stderr, "------------- color special\n"); */
	if (resource.use_color)
	    color_special(cp + 6);
	return;
    }
#endif
    
    /* these should have been scanned */
    if (*cp == '!'
	|| (memicmp(cp, "header", 6) == 0 && endofcommand(cp + 6) != NULL)) {
#ifdef PS
	if (resource.postscript != 0 && scanned_page_reset >= 0) {
	    /* turn on scanning and redraw the page */
	    scanned_page = scanned_page_ps = scanned_page_reset = -1;
# if COLOR
	    scanned_page_color = scanned_page;
# endif
	    globals.ev.flags |= EV_NEWPAGE;		/* force a redraw */
	    longjmp(globals.ev.canit, 1);
	}
#endif /* PS */
	return;
    }

    if (memicmp(cp, "background ", 11) == 0) {
#if COLOR
	if (resource.use_color && scanned_page_reset >= 0) {
	    /* turn on scanning and redraw the page */
	    scanned_page = scanned_page_color = scanned_page_reset = -1;
# if PS
	    scanned_page_ps = scanned_page;
# endif
	    /* 	    fprintf(stderr, "forcing redraw!\n"); */
	    globals.ev.flags |= EV_NEWPAGE;		/* force a redraw */
	    longjmp(globals.ev.canit, 1);
	}
#endif /* COLOR */
	return;
    }

    if (memcmp(cp, "papersize", 9) == 0 && endofcommand(cp + 9) != NULL) {
	m_have_papersize_special = True;
	if (scanned_page_reset >= 0) {
	    /* turn on scanning and redraw the page */
	    scanned_page = scanned_page_reset = -1;
#if PS
	    scanned_page_ps = scanned_page;
#endif
#if COLOR
	    scanned_page_color = scanned_page;
#endif
	    globals.ev.flags |= EV_NEWPAGE; /* force a redraw */
	    longjmp(globals.ev.canit, 1);
	}
	return;
    }

    /* tpic specials */

    if (*cp >= 'a' && *cp <= 'z' && cp[1] >= 'a' && cp[1] <= 'z' &&
	(isspace((int)cp[2]) || cp[2] == '\0')) {
	switch (CMD(*cp, cp[1])) {
	case CMD('p', 'n'):
	    set_pen_size(cp + 2);
	    return;
	case CMD('f', 'p'):
	    flush_path();
	    return;
	case CMD('d', 'a'):
	    flush_dashed(cp + 2, False);
	    return;
	case CMD('d', 't'):
	    flush_dashed(cp + 2, True);
	    return;
	case CMD('p', 'a'):
	    add_path(cp + 2);
	    return;
	case CMD('a', 'r'):
	    arc(cp + 2, False);
	    return;
	case CMD('i', 'a'):
	    arc(cp + 2, True);
	    return;
	case CMD('s', 'p'):
	    flush_spline();
	    return;
	case CMD('s', 'h'):
	    shade_last();
	    return;
	case CMD('w', 'h'):
	    whiten_last();
	    return;
	case CMD('b', 'k'):
	    blacken_last();
	    return;
	case CMD('i', 'p'):	/* throw away the path -- jansteen */
	    path_len = 0;
	    return;
	}
    }

    if (memcmp(cp, "src:", 4) == 0) {
	have_src_specials = True;
    }
    else if (globals.warn_spec_now)
	XDVI_WARNING((stderr, "Special \"%s\" not implemented.", cp));
}

#undef	CMD


Boolean
scan_special(char *cp, int cp_len, void *data)
{
    char *p;
    Boolean dummy_ret = True; /* currently unused;
				 FIXME: use return value to avoid redundant calls (instead of longjmp()) */
    UNUSED(cp_len); /* TODO: could probably utilize this in call to htex_prescan_special() */
    ASSERT(data != NULL, "Must pass a data pointer when using HTEX");
    
    if (globals.debug & DBG_PS)
	printf("Scanning special `%s'.\n", cp);

    while (isspace((int)*cp))
	++cp;
    
    /* Ignore initial "xdvi:" */
    if (memcmp(cp, "xdvi:", 5) == 0) {
	cp += 5;
	while (isspace((int)*cp))
	    ++cp;
    }

    if (memicmp(cp, "ps:", 3) == 0) {
	Boolean found;
	cp += 3;
	/* check for hdvips hyperlinks */
	if (memicmp(cp, "sdict begin ", strlen("sdict begin ")) == 0) {
	    static Boolean hypertex_too_old = False;
	    char *match = NULL;
	    cp += strlen("sdict begin ");

	    if (strstr(cp, "HyperStart") != NULL)
		hypertex_too_old = True;
	    if (hypertex_too_old)
		return False;
	    
	    if (memcmp(cp, "H.S", 3) == 0
		|| memcmp(cp, "H.R", 3) == 0
		|| memcmp(cp, "H.B", 3) == 0
		/* following 2 conditions could be more restrictive: between `begin' and H.A/H.L,
		   there should be a single number (baselineskip in pt) */
		|| (match = strstr(cp, "H.A")) != NULL
		|| (match = strstr(cp, "H.L")) != NULL
		|| (match = strstr(cp, "/Action")) != NULL
		|| (match = strstr(cp, "/Link")) != NULL
		|| (match = strstr(cp, "/View")) != NULL) {
		if (match != NULL)
		    found = htex_prescan_special(match, cp_len, data);
		else
		    found = htex_prescan_special(cp, cp_len, data);
	    }
	}
	    
    }
    else if (memicmp(cp, "html:", strlen("html:")) == 0) {
	Boolean found;
	size_t offset = strlen("html:");
	found = htex_prescan_special(cp + offset, cp_len - offset, data);
	/* if searching for a specific string, return as soon as it's found - not yet implemented */
#if 0
 	if (my_data != NULL && my_data->scan_type == HTEX_ANCHOR_STRING && found) {
 	    return True;
 	}
#endif
    }
    /* do the following only if not searching for an anchor string */
    if (((struct htex_prescan_data *)data)->scan_type != HTEX_ANCHOR_STRING) {
#if PS
# if COLOR
	if (scanned_page_ps <= scanned_page)
# endif
	{
	    if (*cp == '!') {
		scan_bang(cp);
		return dummy_ret;
	    }
	    else if (memicmp(cp, "header", 6) == 0 && (p = endofcommand(cp + 6)) != NULL) {
		scan_header(p);
		return dummy_ret;
	    }
	}
#endif /* PS */
	
#if COLOR
# if PS
	if (scanned_page_color <= scanned_page)
# endif
	{
	    if (memicmp(cp, "background ", 11) == 0) {
		scan_bg_color(cp);
		return dummy_ret;
	    }
	    else if (memicmp(cp, "color ", 6) == 0) {
		scan_color(cp);
		return dummy_ret;
	    }
	}
#endif /* COLOR */
	if (memcmp(cp, "papersize", 9) == 0 && (p = endofcommand(cp + 9)) != NULL) {
	    m_have_papersize_special = True;
	    scan_papersize(p);
	    return dummy_ret;
	}
    }
    return dummy_ret;
}


/* #define	xspell_conv(n)	spell_conv0(n, current_dimconv) */
#define	xpixel_conv(x)	((int) ((x) >> 16))
#define	G_PXL_H		xpixel_conv(currinf.data.dvi_h)

/* cp is not const, because of endofcommand(). */
void
geom_do_special(struct scan_info *info, char *cp, double current_dimconv)
{
    const char *p;
    struct geom_info *g_info = (struct geom_info *)info->data;
    
    UNUSED(current_dimconv);
    
    while (isspace((int)*cp))
	++cp;

    /* Ignore initial "xdvi:" */
    if (memcmp(cp, "xdvi:", 5) == 0) {
	cp += 5;
	while (isspace((int)*cp))
	    ++cp;
    }

    if (memicmp(cp, "psfile", 6) == 0 && (p = endofcommand(cp + 6)) != NULL) {
	/* compute epsf bounding box */
	char c;
	int flags = 0;
	double keyval[6];

	c = *p;
	if (c == '\'' || c == '"') {
	    do
		++p;
	    while (*p != '\0' && *p != c);
	}
	else
	    while (*p != '\0' && !isspace((int)*p))
		++p;
	while (isspace((int)*p))
	    ++p;
	while (*p != '\0') {
	    const char *p1 = p;
	    size_t keyno;

	    while (*p1 != '=' && !isspace((int)*p1) && *p1 != '\0')
		++p1;
	    for (keyno = 0; keyno < NKEYS; ++keyno) {
		if (memcmp(p, keytab[keyno], p1 - p) == 0) {
		    if (keyno >= N_ARGLESS_KEYS) {
			while (isspace((int)*p1))
			    ++p1;
			if (*p1 == '=') {
			    ++p1;
			    while (isspace((int)*p1))
				++p1;
			}
			if (keyno < N_ARGLESS_KEYS + 6) {
			    keyval[keyno - N_ARGLESS_KEYS] = atof(p1);
			    flags |= (1 << (keyno - N_ARGLESS_KEYS));
			}
			while (!isspace((int)*p1) && *p1 != '\0')
			    ++p1;
		    }
		    break;
		}
	    }
	    p = p1;
	    while (!isspace((int)*p) && *p != '\0')
		++p;
	    while (isspace((int)*p))
		++p;
	}

	if ((flags & 0x30) == 0x30 || ((flags & 0x30) && (flags & 0xf) == 0xf)) {
	    long x = G_PXL_H;
	    long y = PXL_V;
	    long bbox_w;
	    long bbox_h;

	    bbox_w = 0.1 * ((flags & 0x10) ? KEY_RWI
			    : KEY_RHI * (KEY_URX - KEY_LLX) / (KEY_URY -
							       KEY_LLY)) *
		dimconv + 0.5;
	    bbox_h =
		0.1 * ((flags & 0x20) ? KEY_RHI : KEY_RWI *
		       (KEY_URY - KEY_LLY) / (KEY_URX - KEY_LLX))
		* dimconv + 0.5;

	    g_info->geom_box(info, x, y - bbox_h, x + bbox_w, y);
	}
    }
    else if (memicmp(cp, "ps::[begin]", 11) == 0) {
	/* compute psfig bounding box */
	long bbox_w, bbox_h;

	if (sscanf(cp + 11, "%ld %ld\n", &bbox_w, &bbox_h) >= 2) {
	    long x = G_PXL_H;
	    long y = PXL_V;

	    bbox_w = xpixel_conv(spell_conv(bbox_w));
	    bbox_h = xpixel_conv(spell_conv(bbox_h));

	    g_info->geom_box(info, x, y, x + bbox_w, y + bbox_h);
	}
    }
}
