/*
 *	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, altough it may
 *	be distributed with other software which is sold. If the
 *	software is distributed, the source code must be made available.
 *
 *	No warrenty, expressed or implied, is given with this software.
 *	It is presented in the hope that it will prove useful.
 */

#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <suntool/sunview.h>
#include <suntool/canvas.h>
#include <sunwindow/defaults.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sun/fbio.h>
#include "dvistuff.h"
/*
 *	These constants may need to be changed on your implementation
 */

/*
 *	Note that ROUNDUP evaluates Y twice.
 */
#define ROUNDUP(x,y) (((x)+(y)-1) / (y))

#define DEBUG printf

#define INFORM (Debug > 2)
#define TALK (Debug > 1)
#define VERBOSE (Debug > 0)

int Debug = 0;
static int  obscured = 1;


/*
 *	SunTools related variables
 */

static Window tool = 0;
static Canvas ptubeWindow = 0;
static Pixwin *ptube = 0;

static int canvasHeight;
static int canvasWidth;
static int canvasNormalWidthOffset;
static int canvasNormalHeightOffset;

typedef int (*intFuncp)();
intFuncp memPixmapOps;

static int foregroundMode = PIX_SRC | PIX_DST;
static int clearMode = PIX_CLR;
static int setMode = PIX_SET;
static int copyMode = PIX_SRC;
static int paintMode;
static int cursorMode;

static int globalArg = 0;
static int globalNumber = 0;
static int globalSign = 1;
static int globalOtherLeaf = 0;
static int globalLeafX;
static int globalLeafY;
static int globalDisplayState;
static int globalDisplayLeaf;

static int firstPage = 0;
static char *dviFileName = 0;

static Cursor workingCursor;
static Cursor readyCursor;

short workingCursorData[] = {
#include <images/hglass.cursor>;
};
mpr_static(workingCursorPixrect, 16, 16, 1, workingCursorData);

short readyCursorData[] = {
#include <images/bullseye.cursor>;
};
mpr_static(readyCursorPixrect, 16, 16, 1, readyCursorData);

/*
 *	TeX-Dvi related variables
 */

static int rawDviHeight;
static int rawDviWidth;	/* part of X interface, not dvistuff */
static int maxHeight;
static int maxWidth;
static int maxDepth;

static int  screenWidth, screenHeight;

#define MAX_LEAVES	2
#define LEAF_LEFT	0
#define LEAF_RIGHT	1

#define DEFAULT_LEAVES	2

static int Leaves;
static int currentLeaf = 0;
static int pageOnLeaf[MAX_LEAVES] = {-1,-1};
static int haveLargePage[MAX_LEAVES] = {-1, -1};

#define SHRINK_NORMAL	0
#define SHRINK_LARGE	1
#define MAX_SHRINKS	2

static int page_w[MAX_SHRINKS], page_h[MAX_SHRINKS];
static int leaf_w[MAX_SHRINKS];

static int shrinkFactor[MAX_SHRINKS];
static int currentShrink;
static struct glyph **shrunkenGlyphs[MAX_SHRINKS][MAX_FONTFAMILY];

typedef struct mpr_data MprData;
static Pixrect **shrunkenPixrect[MAX_SHRINKS][MAX_FONTFAMILY];

static int reverse = 0;

static double specialConv;

#define TEXSUN_STATE_NAME "TEXSUN_STATE"
#define maxStateLines 100
char *stateFileName;

char **globalArgv;
int globalArgc;

char *malloc(), *calloc(), *index();

#define MAXFUNC(a,b) ((a) < (b)) ? (b) : (a)
#define MINFUNC(a,b) ((a) < (b)) ? (a) : (b)

stop_output()
{
/*
 *	We must write the state before killing the window
 */
    writeState(stateFileName);

    window_set(tool, FRAME_NO_CONFIRM, TRUE, 0);
    window_destroy (tool);
    exit (0);
}

main(argc, argv)
    int argc;
    char **argv;
{
    
    int xargc=argc;
    char **xargv=argv;

    char *display = NULL;
    char option[120];
    int optionEnd;
    char *poption;
    
    int bwidth = 2;
    char *geometry = NULL, def[32];
    
    int bdrpix, mouspix;
    unsigned long xswattrs_mask;

    struct rect tool_rect;
    int ptubeSelected();
    int repaintCanvas();
    int resizeCanvas();

    double atof();
    char *getenv();
    
/*
 *	Choose an explicit name so we always find the options.
 */

    ProgName = "texsun";
    argv++;
    argc--;
    dviFileName = NULL;

    strcpy(option, ProgName);
    sprintf(option,"/%s/", ProgName);
    optionEnd = strlen(option);
    option[optionEnd] = 0;
    reverse = (int) defaults_get_boolean(strcat(option,"ReverseVideo"), 0, 0);

    option[optionEnd] = 0;
    shrinkFactor[SHRINK_NORMAL] =
	defaults_get_integer(strcat(option,"NormalShrink"), 0, 0);

    option[optionEnd] = 0;
    shrinkFactor[SHRINK_LARGE] =
	defaults_get_integer(strcat(option,"LargeShrink"), 0, 0);

    option[optionEnd] = 0;
    dviDPI = defaults_get_integer(strcat(option,"Dpi"), DEFAULT_DPI, 0);

    option[optionEnd] = 0;
    Leaves = defaults_get_integer(strcat(option,"Leaves"), 0, 0);

    option[optionEnd] = 0;
    poption = defaults_get_string(strcat(option,"TopMargin"), 0, 0);
    dviVVMargin = DEFAULT_VVMARGIN * ( (poption == 0) ? 1.0 : atof(poption));

    option[optionEnd] = 0;
    poption = defaults_get_string(strcat(option,"SideMargin"), 0, 0);
    dviHHMargin = DEFAULT_HHMARGIN * ( (poption == 0) ? 1.0 : atof(poption));

    option[optionEnd] = 0;
    dviBlackness = defaults_get_integer(strcat(option,"Blackness"),
					DEFAULT_BLACKNESS, 0);

    option[optionEnd] = 0;
    stateFileName = defaults_get_string(strcat(option,"StateFile"), 0, 0);

    if (stateFileName == 0) {
	stateFileName = getenv(TEXSUN_STATE_NAME);
    }

    if (argc > 0 && strcmp(argv[0], "-new") == 0) {
	argv++;
	argc--;
    } else {
	readState(&argc, &argv, stateFileName);
    }

    globalArgc = argc;
    globalArgv = argv;

    while (argc) {
	if (strcmp(*argv, "-rv") == 0) {
	    reverse = !reverse;
/*
 *	these two are for the state-saver
 */

	} else if (strcmp(*argv, "-rvOn") == 0 ) {
	    reverse = 1;
	} else if (strcmp(*argv, "-rvOff") == 0) {
	    reverse = 0;
	} else if (strcmp(*argv,"-l") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    Leaves = atoi(*argv);
	    if( Leaves < 1 || Leaves > MAX_LEAVES) {
		fprintf(stderr,"[%s] Bad number of leaves(%d), using %d\n",
			ProgName, Leaves, MAX_LEAVES);
		Leaves = MAX_LEAVES;
	    }
	} else if (strcmp(*argv,"-ns") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    shrinkFactor[SHRINK_NORMAL] = atoi(*argv);
	} else if (strcmp(*argv,"-ls") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    shrinkFactor[SHRINK_LARGE] = atoi(*argv);
	} else if (strcmp(*argv,"-dpi") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    dviDPI = atoi(*argv);
	} else if (strcmp(*argv,"-tm") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    dviVVMargin = dviDPI * atof(*argv);
	} else if (strcmp(*argv,"-sm") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    dviHHMargin = dviDPI * atof(*argv);
	} else if (strcmp(*argv, "-bl") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    dviBlackness = atoi(*argv);
	} else if (strcmp(*argv, "-pg") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    firstPage = atoi(*argv);
	} else if (strcmp(*argv,"-debug") == 0 && argc > 1) {
	    argv++;
	    argc--;
	    Debug = atoi(*argv);
	} else if (**argv != '-') {
	    dviFileName = *argv;
	} else {
	usage:
	    fprintf(stderr, "Usage: %s	[-ns <shrink>] [-ls <shrink>] [-dpi <dots>] \n",
		    ProgName);
	    fprintf(stderr, "		[-l <leaves>] [-rv] [-tm <inches>] [-sm <inches>]  dviFile\n");
	    exit(1);
	}
	argv++;
	argc--;
    }

    if (dviFileName == NULL)
	goto usage;

    if ((dviFile = fopen(dviFileName, "r")) == NULL) {
	int n = strlen(dviFileName);
	char *dvi_name;
	
	if (strcmp(dviFileName + n - sizeof(".dvi") + 1, ".dvi") == 0) {
	    perror(dviFileName);
	    exit(1);
	}
	dvi_name = malloc((unsigned) n + sizeof(".dvi"));
	sprintf(dvi_name, "%s.dvi", dviFileName);

	if ((dviFile = fopen(dvi_name, "r")) == NULL) {
	    perror(dvi_name);
	    exit(1);
	}

	dviFileName = dvi_name;
    }

    if (INFORM) {
	fprintf(stderr, "[texsun] call dviInit();\n");
    }

    dviInit();

    if (TALK) {
	fprintf(stderr,"page is %d wide, %d high\n",
		dviTallestPage, dviWidestPage);
    }

    if (((double) dviTallestPage * (double) dviWidestPage) > 4.0e7) {
	fprintf(stderr,"[%s] Warning: Your page size is %d wide and %d tall,\n",
		ProgName, dviTallestPage, dviWidestPage);
	fprintf(stderr,"which may be too big to be displayed\n");
    }

    specialConv = (float) dviDPI / 1000.0;
    
/*
 *	If no shrink factor was chosen, pick one which will work out nicely
 *	on their display
 */
    if (INFORM) {
	fprintf(stderr, "[texsun] set screenSpecific\n");
    }

    setScreenSpecifics();
    
    rawDviHeight = dviTallestPage + 2 * dviVVMargin;
    rawDviWidth  = dviWidestPage + 2 * dviHHMargin;
    
    if (Leaves == 0) {
	if (dviTotalPages == 1) {
	    Leaves = 1;
	} else {
	    Leaves = DEFAULT_LEAVES;
	}
    }

    if (INFORM) {
	fprintf(stderr, "[texsun] choose screenSpecific\n");
    }

    if (shrinkFactor[SHRINK_NORMAL] == 0) {
	int sH;
	int sW;
	int sH2;
	int sW2;
	int shrink2;
	
	sH = (rawDviHeight + maxHeight - 1) / maxHeight;
	sW = (Leaves * rawDviWidth  + maxWidth - 1) / maxWidth;
	
	shrinkFactor[SHRINK_NORMAL] = MAXFUNC(sW, sH);
	shrinkFactor[SHRINK_NORMAL] = MAXFUNC(shrinkFactor[SHRINK_NORMAL], 1);
	
/*
 *	Check to see if we can get another shrink size bigger display
 *	if we cut the margins.
 */
	
	sH2 = (dviTallestPage + (dviHHMargin/32) + maxHeight - 1)
	    / maxHeight;
	sW2 = ( Leaves * (dviWidestPage + (dviVVMargin/32))
	       + maxHeight - 1) / maxHeight;
	
	shrink2 = MAXFUNC(sH2, sW2);
	shrink2 = MAXFUNC(shrink2, 1);
	
	if (shrink2 < shrinkFactor[SHRINK_NORMAL]) {
	    dviVVMargin /= 32;
	    dviHHMargin /= 32;
	    rawDviHeight = dviTallestPage + 2 * dviVVMargin;
	    rawDviWidth  = dviWidestPage + 2 * dviHHMargin;
	    shrinkFactor[SHRINK_NORMAL] = shrink2;
	}
    }
    
    page_h[SHRINK_NORMAL]=(rawDviHeight + shrinkFactor[SHRINK_NORMAL] - 1)
	/ shrinkFactor[SHRINK_NORMAL];
    leaf_w[SHRINK_NORMAL]=(rawDviWidth + shrinkFactor[SHRINK_NORMAL] - 1)
	/ shrinkFactor[SHRINK_NORMAL];

/*
 *	Based on the shrink factor, choose a shrink factor for the enlarged
 *	display
 */

    if (shrinkFactor[SHRINK_LARGE] == 0) {
	shrinkFactor[SHRINK_LARGE] = shrinkFactor[SHRINK_NORMAL] / 2;
    }

    shrinkFactor[SHRINK_LARGE] = MINFUNC(shrinkFactor[SHRINK_LARGE],
				     shrinkFactor[SHRINK_NORMAL]-1);
    shrinkFactor[SHRINK_LARGE] = MAXFUNC(shrinkFactor[SHRINK_LARGE], 1);

    page_h[SHRINK_LARGE]=(rawDviHeight + shrinkFactor[SHRINK_LARGE] - 1)
	/ shrinkFactor[SHRINK_LARGE];
    leaf_w[SHRINK_LARGE]=(rawDviWidth + shrinkFactor[SHRINK_LARGE] - 1)
	/ shrinkFactor[SHRINK_LARGE];

/*
 *	Compute the page size given the number of leaves. We may have
 *	to scale back if everything cant fit.
 */
    
    if (leaf_w[SHRINK_NORMAL] * Leaves <= maxWidth) {
	page_w[SHRINK_NORMAL] = leaf_w[SHRINK_NORMAL] * Leaves;
    } else {
	page_w[SHRINK_NORMAL] = leaf_w[SHRINK_NORMAL];
	Leaves = 1;
    }
    
    screenWidth = (page_w[SHRINK_NORMAL] > maxWidth)
	? maxWidth : page_w[SHRINK_NORMAL];
    screenHeight = (page_h[SHRINK_NORMAL] > maxHeight)
	? maxHeight : page_h[SHRINK_NORMAL];
    
    tool_rect.r_left = 0;
    tool_rect.r_top = 0;
    tool_rect.r_width = 0;
    tool_rect.r_height = 0;

    cursorMode = PIX_SRC ^ PIX_DST;

    if (reverse) {
	paintMode = PIX_NOT(PIX_SRC);
    } else {
	paintMode = PIX_SRC;
    }

    if (INFORM) {
	fprintf(stderr,"[texsun] create the tool windows\n");
    }

/* create the tool */
    tool = window_create (0, FRAME,
			  FRAME_LABEL, "TeXsun",
			  FRAME_OPEN_RECT, &tool_rect,
			  0);
    if (tool == 0) {
	fprintf(stderr,"Unable to create window\n");
	exit(1);
    }
    ptubeWindow = window_create(tool, CANVAS,
				CANVAS_AUTO_SHRINK, TRUE,
				CANVAS_AUTO_EXPAND, TRUE,
				CANVAS_WIDTH, screenWidth,
				WIN_FIT_WIDTH, screenWidth,
				CANVAS_HEIGHT, screenHeight,
				WIN_FIT_HEIGHT, screenHeight,
				CANVAS_RETAINED, FALSE,
				CANVAS_REPAINT_PROC, repaintCanvas,
				CANVAS_RESIZE_PROC, resizeCanvas,
				WIN_EVENT_PROC, ptubeSelected,
				WIN_BOTTOM_MARGIN, 0,
				WIN_LEFT_MARGIN, 0,
				WIN_TOP_MARGIN, 0,
				WIN_RIGHT_MARGIN, 0,
				WIN_X, 0,
				WIN_Y, 0,
				0);
    if (ptubeWindow == 0) {
	fprintf(stderr,"Unable to create canvas\n");
	exit(1);
    }

    readyCursor = cursor_create(CURSOR_IMAGE, &readyCursorPixrect,
				CURSOR_OP, cursorMode, 0);
    workingCursor = cursor_create(CURSOR_IMAGE, &workingCursorPixrect,
				  CURSOR_OP, cursorMode, 0);

    ptubeInit();

    window_set(ptubeWindow, WIN_CONSUME_KBD_EVENT, WIN_ASCII_EVENTS,0);

    signal (SIGINT, stop_output);
    signal (SIGQUIT, stop_output);
    signal (SIGIOT, stop_output);

    window_fit(ptubeWindow);
    window_fit(tool);

    if (INFORM) {
	fprintf(stderr,"[texsun] enter window loop\n");
    }

    window_main_loop (tool);

    /* terminate tool */
    stop_output(0);
}

/*
 *	State-maintenance routines.
 *
 *	The state is maintained in a file indicated by the environment
 *	variable TEXSUN_STATE.
 *
 *	The state is written as a sequence of options, the same options
 *	which can be specified on the command line.
 *
 *	When the state is used, the saved options are inserted in the
 *	command line before any new options. Thus, new options will
 *	over-ride the saved options, and both saved state and command
 *	line options will over-ride the default options.
 */

readState(inOutArgc, inOutArgv, stateFileName)
char ***inOutArgv;
int *inOutArgc;
char *stateFileName;
{
    if (stateFileName != 0) {
	FILE *f;
	char **newArgv;
	char *p;
	int newArgc;
	char buffer[512];
	char *bufp;
	int i;

	int argc = *inOutArgc;
	char **argv = *inOutArgv;

	newArgv = (char **) malloc( sizeof(char *) * (argc + maxStateLines));
	f = fopen(stateFileName,"r");

/*
 *	The program name has already been removed.
 */

	newArgc = 0;

	if (f != 0) {
	    while (!feof(f)) {
		p = fgets(buffer, 512, f);
		if (p != 0) {
		    int lth;
		    lth = strlen(buffer);
		    p = malloc(lth + 1);
		    strcpy(p, buffer);
		    p[lth-1] = 0;	/* kill the newline character */
		    newArgv[newArgc++] = p;
		}
	    }

	    for (i = 0 ; i < argc; i++ ) {
		newArgv[newArgc++] = argv[i];
	    }

	    *inOutArgc = newArgc;
	    *inOutArgv = newArgv;
	}
    }
}

writeState(stateFileName)
char *stateFileName;
{
    if (stateFileName != 0) {
	FILE *f;
	char *ptr;

	f = fopen(stateFileName,"w");
	if (f != 0) {
	    fprintf(f, "-rv%s\n", (reverse) ? "On" : "Off");
	    fprintf(f, "-l\n%d\n",  Leaves);
	    fprintf(f, "-ns\n%d\n", shrinkFactor[SHRINK_NORMAL]);
	    fprintf(f, "-ls\n%d\n", shrinkFactor[SHRINK_LARGE]);
	    fprintf(f, "-dpi\n%d\n", dviDPI);
	    fprintf(f, "-tm\n%f\n", (double)
		    ((double) dviVVMargin / (double) dviDPI));
	    fprintf(f, "-sm\n%f\n", (double)
		    ((double) dviHHMargin / (double) dviDPI));
	    fprintf(f, "-bl\n%d\n", dviBlackness);
	    fprintf(f, "-debug\n%d\n", Debug);
	    fprintf(f, "-pg\n%d\n", pageOnLeaf[0]);
	    fprintf(f, "%s\n", dviFileName);

	    fclose(f);
	}
    }
}


/*
 *	Sun Screen-specific things
 */
setScreenSpecifics()
{
    struct fbtype FbType;
    int Fb;    

    Fb = open("/dev/fb", O_RDONLY, 0);
    
    if (Fb < 0) {
	perror("open");
	fprintf(stderr,"Unable to open /dev/fb\n");
	exit(1);
    }
    if (ioctl(Fb, FBIOGTYPE, &FbType) == -1) {
	perror("ioctl");
	fprintf(stderr,"Unable to determine screen size\n");
	exit(1);
    }

    maxWidth = FbType.fb_width;
    maxHeight = FbType.fb_height;
    maxDepth = FbType.fb_depth;

    close(Fb);
}

resizeCanvas(canvas, width, height)
{
    canvasWidth = width;
    canvasHeight = height;

    if (canvasWidth > (Leaves * leaf_w[SHRINK_NORMAL])) {
	canvasNormalWidthOffset =
	    (canvasWidth - Leaves * leaf_w[SHRINK_NORMAL]);
    }
    if (canvasHeight > (Leaves * page_h[SHRINK_NORMAL])) {
	canvasNormalHeightOffset =
	    (canvasHeight - Leaves * page_h[SHRINK_NORMAL]);
    }
}

repaintCanvas(canvas, pixwin, repaint_area)
Canvas canvas;
Pixwin *pixwin;
Rectlist *repaint_area;
{
    displayLeaves();
}

/*
 *	Pixmap manipulation routines
 */

static Pixrect *pasteUpPixmap[MAX_SHRINKS][MAX_LEAVES];
static int largePixmapsAllocated = 0;

/*
 *	Ideally, we'd like to paint up a local pixmap & then transmit the
 *	entire pixmap. However, it looks like pixmaps require interaction
 *	with the server.
 *
 *	So, if the entire page fits on the screen, we'll paint the screen
 *	and when we're done, we'll copy the screen to the pixmap. This
 *	gives the illusion of progress, but allows us to do fast re-paints.
 *
 *	If we're *not* on the display, we'll just draw to the pixmap and
 *	update it at the end
 */

static int buildToPixmap = 0;

static void
clearPixmap(leaf, shrinkSize)
int leaf;
int shrinkSize;
{
    Pixrect *w;

    if (leaf< 0 || leaf >= Leaves) {
	fprintf(stderr,"[%s] Reference to bogus leaf in clearPixmap: %d\n",
		ProgName, leaf);
	exit(1);
    }

    if (shrinkSize < 0 || shrinkSize >= MAX_SHRINKS ) {
	fprintf(stderr,"[%s] reference to bogus shrink %d\n",
		ProgName, shrinkSize);
	exit(1);
    }

    w = pasteUpPixmap[shrinkSize][leaf];
    pr_rop(w, 0, 0,
	     leaf_w[shrinkSize], page_h[shrinkSize],
	     clearMode,
	     w, 0, 0);
}

/*
 *	Clear the window. Actually, for a black-on-white display (normal)
 *	this means we need to *set* the entire display using COLOR(1) color.
 */
static void
clearWindow()
{
    int localClear = PIX_COLOR(1) | clearMode;
    if (reverse) {
	localClear = PIX_NOT(localClear);
    }
    pw_writebackground(ptube, 
		       0, 0,
		       canvasWidth, canvasHeight,
		       localClear);
}

allocatePixmaps()
{
    int i;

    for (i = 0; i < Leaves; i++ ) {
	pasteUpPixmap[SHRINK_NORMAL][i] = (Pixrect *)
	    mem_create(leaf_w[SHRINK_NORMAL], page_h[SHRINK_NORMAL], 1);

	if (pasteUpPixmap[SHRINK_NORMAL][i] == 0) {
	    fprintf(stderr,"[%s] erp! out of PIXMAP memory!\n",
		    ProgName);
	    stop_output();
	}
    }

    largePixmapsAllocated = 0;

/*
 *	Clear the pixmap
 */
    for (i = 0; i < Leaves; i++) {
	clearPixmap(i, SHRINK_NORMAL);
    }
}

allocateLargePixmaps()
{
    int i;
    for (i = 0; i < Leaves; i++) {
	pasteUpPixmap[SHRINK_LARGE][i] = (Pixrect *)
	    mem_create(leaf_w[SHRINK_LARGE], page_h[SHRINK_LARGE], 1);
	clearPixmap(i, SHRINK_LARGE);
    }

    largePixmapsAllocated = 1;
}

/*
 *	display the normal sized leaf
 */
static int
displayLeaves()
{
    if (globalDisplayState == SHRINK_LARGE) {
	    
/*
 *	globalLeafX and globalLeafY point the part of the display
 *	they want to see, scaled to the large shrink.
 *
 */
	int leafX;
	int leafY;
	int leafW;
	int leafH;
	int canvasX;
	int canvasY;

	leafW = MINFUNC(leaf_w[SHRINK_LARGE], canvasWidth);
	leafH = MINFUNC(page_h[SHRINK_LARGE], canvasHeight);
	
	leafX = globalLeafX - (leafW / 2);	/* find left side */
	if (leafX < 0) {
	    canvasX = -leafX;
	    leafX = 0;
	} else {
	    canvasX = 0;
	}
	    
	if ((leafX + leafW) > leaf_w[SHRINK_LARGE]) {
	    leafW = leaf_w[SHRINK_LARGE] - leafX;
	}
	    
	leafY = globalLeafY - (leafH / 2);	/* gind top side */
	if (leafY < 0) {
	    canvasY = -leafY;
	    leafY = 0;
	} else {
	    canvasY = 0;
	}
	if ((leafY + leafH) > page_h[SHRINK_LARGE]) {
	    leafH = page_h[SHRINK_LARGE] - leafY;
	}

#ifdef UNDEF
	if (leafW < canvasWidth) {
	    leafX += (canvasWidth - leafW) / 2;
	}
	if (leafH < canvasHeight) {
	    leafY += (canvasHeight - leafH) / 2;
	}
#endif
	
	clearWindow();
	pw_write(ptube,
		 canvasX, canvasY,
		 leafW, leafH,
		 paintMode,
		 pasteUpPixmap[SHRINK_LARGE][globalDisplayLeaf],
		 leafX, leafY);
    } else {
	clearWindow();
	pw_write(ptube,
		 0, 0,
		 leaf_w[SHRINK_NORMAL], page_h[SHRINK_NORMAL],
		 paintMode,
		 pasteUpPixmap[SHRINK_NORMAL][0],
		 0,0);
	pw_write(ptube,
		 leaf_w[SHRINK_NORMAL], 0,
		 leaf_w[SHRINK_NORMAL], page_h[SHRINK_NORMAL],
		 paintMode,
		 pasteUpPixmap[SHRINK_NORMAL][1],
		 0,0);
    }
}

static void
swapLeaf(from, to)
int from;
int to;
{
    Pixrect *tempPix;
    int tempNum;

    if (to < 0 || to >= Leaves) {
	fprintf(stderr,"[%s] bogus to leaf %d in swapLeaf\n",
		ProgName, to);
	exit(1);
    }
    if (from < 0 || from >= Leaves) {
	fprintf(stderr,"[%s] bogus from leaf %d in swapLeaf\n",
		ProgName, from);
	exit(1);
    }

    tempPix = pasteUpPixmap[SHRINK_NORMAL][to];
    pasteUpPixmap[SHRINK_NORMAL][to] = pasteUpPixmap[SHRINK_NORMAL][from];
    pasteUpPixmap[SHRINK_NORMAL][from] = tempPix;

    tempPix = pasteUpPixmap[SHRINK_LARGE][to];
    pasteUpPixmap[SHRINK_LARGE][to] = pasteUpPixmap[SHRINK_LARGE][from];
    pasteUpPixmap[SHRINK_LARGE][from] = tempPix;

    tempNum = pageOnLeaf[to];
    pageOnLeaf[to] = pageOnLeaf[from];
    pageOnLeaf[from] = tempNum;

    tempNum = haveLargePage[to];
    haveLargePage[to] = haveLargePage[from];
    haveLargePage[from] = tempNum;
}

static void
buildLeaf(leaf, page, shrinkSize)
int leaf;
int page;
int shrinkSize;
{

    if (leaf < 0 || leaf >= Leaves) {
	fprintf(stderr,"[%s] bogus leaf %d in buildLeaf\n",
		ProgName, leaf);
	exit(1);
    }

    if (shrinkSize < 0 || shrinkSize >= MAX_SHRINKS ) {
	fprintf(stderr,"[%s] Bogus shrink size %d in buildLeaf\n",
		ProgName, shrinkSize);
	exit(1);
    }

    window_set(ptubeWindow, WIN_CURSOR, workingCursor, 0);

    if (shrinkSize == SHRINK_LARGE && !largePixmapsAllocated) {
	allocateLargePixmaps();
    }

/*
 *	If this is a normal size page, kill the information
 *	for the large version.
 */
    if (shrinkSize == SHRINK_NORMAL) {
	haveLargePage[leaf] = -1;
    }

/*
 *	Determine if this is a valid page. If it's not, we'll just clear
 *	the particular leaf.
 */

    if (page < 0 || page >= dviTotalPages) {
	clearPixmap(leaf, shrinkSize);

	if (shrinkSize == SHRINK_NORMAL) {
	    clearWindow(leaf);
	}
    } else {
	
	if (obscured || shrinkSize == SHRINK_LARGE) {
	    buildToPixmap = 1;	
	} else {
	    buildToPixmap = 1;
	}
	
	if (buildToPixmap) {
	    clearPixmap(leaf, shrinkSize);
	} else {
	    clearPixmap(leaf, shrinkSize);
	    if (shrinkSize == SHRINK_NORMAL) {
		clearWindow(leaf);
	    }
	}

/*
 *	Note that dviPreparePage may change dviCurrentPage
 */

	currentLeaf = leaf;
	currentShrink = shrinkSize;

	dviPreparePage(page);

	page = dviCurrentPage;

/*
 *	Save the constructed page if we were painting it to the screen
 */
	put_border(0, 0, leaf_w[currentShrink], page_h[currentShrink], 1);

	if (! buildToPixmap && shrinkSize == SHRINK_NORMAL) {
	    clearPixmap(leaf, SHRINK_NORMAL);
/*
 *	We save the pixmap from the *retained* image -- the tool may not
 *	actually be up. This isn't optimal, but it saves us a lot of
 *	hacking & having to determine when we're obscured.
 */
	    pr_rop(pasteUpPixmap[SHRINK_NORMAL][leaf],
		     0,0,
		     leaf_w[SHRINK_NORMAL], page_h[SHRINK_NORMAL],
		     copyMode,
		     ptube -> pw_prretained,
		     leaf_w[SHRINK_NORMAL] * leaf, 0);
	} else if (shrinkSize == SHRINK_NORMAL) {
	    /* null */
	    }
    }

    pageOnLeaf[leaf] = dviCurrentPage;
    window_set(ptubeWindow, WIN_CURSOR, readyCursor, 0);
}
    
/*
 *	interfaces to dvistuff
 */

/*
 *	Whenever a new font is registers, we create a shrunken Glyph
 *	table for it. However, we don't shrink the glyphs -- that's
 *	done on the fly by the putChar routine.
 */

DviFont *
applicationNewFont(f, key)
struct font *f;
int key;
{
    int shrink;

    if (key < 0 || key > MAX_FONTFAMILY) {
	fprintf(stderr,"[%s] bogus key in Newfont = %d\n",
		ProgName, key);
	exit(1);
    }
    
    for (shrink = SHRINK_NORMAL; shrink <= SHRINK_LARGE; shrink++) {
	
	if (shrunkenGlyphs[shrink][key] == 0) {
	    int lth = sizeof(struct glyph *) * MAX_GLYPH;
	    struct glyph **g;
	    
	    g = (struct glyph **) malloc( lth );
	    bzero(g, lth);
	    shrunkenGlyphs[shrink][key] = g;
	}

	if (shrunkenPixrect[shrink][key] == 0) {
	    int lth = sizeof(Pixrect *) * MAX_GLYPH;
	    Pixrect  **g;
	    
	    g = (Pixrect **) malloc( lth );
	    bzero(g, lth);
	    shrunkenPixrect[shrink][key] = g;
	}

    }
    return(f);
}

/*
 *	When we reset a font, we only need to free the storage for the
 *	shrunken glyphs. We keep the glyph table available because we're
 *	very likely to fill it in again.
 */

void
applicationResetFont(fi, key)
struct fontinfo *fi;
int key;
{
    int i;
    struct glyph **theseGlyphs;
    Pixrect **thesePixrect;
    int shrink;
    
    
    for (shrink = SHRINK_NORMAL; shrink <= SHRINK_LARGE; shrink++) {
	theseGlyphs = shrunkenGlyphs[shrink][key];
	if (theseGlyphs != 0) {
	    for (i = 0; i < MAX_GLYPH; i++) {
		struct glyph *g;
		
		g = theseGlyphs[i];
		
		if (g != 0) {
		    if ( g -> g_raster != 0) {
			free(g -> g_raster);
		    }
		    free(g);
		    theseGlyphs[i] = 0;
		}
	    }
	}

	thesePixrect = shrunkenPixrect[shrink][key];
	if (thesePixrect != 0) {
	    for (i = 0; i < MAX_GLYPH; i++) {
		Pixrect *g;
		
		g = thesePixrect[i];
		
		if (g != 0) {
		    free(g -> pr_data);
		    free(g);
		    thesePixrect[i] = 0;
		}
	    }
	}
    }
}


void
applicationPutChar(hh, vv, charCode)
int hh;
int vv;
int charCode;
{
    register struct glyph *g;
    int x,y;
    int key;
    Pixrect *image;
    
    key = dviCurrentFont -> family;

    g = shrunkenGlyphs[currentShrink][key][charCode];

    if (g == 0) {

	MprData *newMpr = (MprData *) malloc(sizeof(MprData));
	Pixrect *newPixrect = (Pixrect *) malloc(sizeof(Pixrect));
	extern struct pixrectops mem_ops;

	g = dviShrinkGlyph(dviCurrentFont -> f -> f_gly[charCode],
			   shrinkFactor[currentShrink],
			   shrinkFactor[currentShrink]);
	shrunkenGlyphs[currentShrink][key][charCode] = g;

	newMpr -> md_linebytes = SHRUNK_GLYPH_BYTES_WIDE(g);
	newMpr -> md_image = (short *) g -> g_raster;
	newMpr -> md_offset.x = 0;
	newMpr -> md_offset.y = 0;
	newMpr -> md_primary = 0;
	newMpr -> md_flags = 0;

	newPixrect -> pr_ops = &mem_ops;
	newPixrect -> pr_size.x = g -> g_width;
	newPixrect -> pr_size.y = g -> g_height;
	newPixrect -> pr_depth =1;
	newPixrect -> pr_data = (caddr_t) newMpr;
	shrunkenPixrect[currentShrink][key][charCode] = newPixrect;
    }

    if (g == 0 || !HASRASTER(g)) return;

    hh /= shrinkFactor[currentShrink];
    vv /= shrinkFactor[currentShrink];

    x = hh - g -> g_xorigin;
    y = vv - g -> g_yorigin;

    image = shrunkenPixrect[currentShrink][key][charCode];

    if (buildToPixmap) {
	pr_rop(pasteUpPixmap[currentShrink][currentLeaf],
		  x,y,
		  g->g_width, g->g_height,
		  foregroundMode,
		  image,
		  0,0);
    } else {
	x += currentLeaf * leaf_w[currentShrink];
	pw_write(ptube,
		  x, y,
		  g->g_width, g->g_height,
		  foregroundMode,
		  image,
		  0,0);
    }
}

void 
applicationSetRule(hh, vv, h, w)
int hh, vv;
int h, w;
{
/* (w,h) specifies lower left corner of rule box */
    int nh, nw;

    hh /= shrinkFactor[currentShrink];
    vv /= shrinkFactor[currentShrink];

    nh = h / shrinkFactor[currentShrink];
    nw = w / shrinkFactor[currentShrink];

    if (nh == 0 && h != 0) {
	nh = 1;
    }
    if (nw == 0 && w != 0) {
	nw = 1 ;
    }

    put_rectangle(hh, vv - nh, nw, nh, setMode);
}

/*
 *	This code attempts to support the same set of specials
 *	as imagen1
 */ 
static char *
skipWhite(c)
char *c;
{
    while (c != 0 && *c != 0 && isspace(*c)) c++;
    return(c);
}

static char *
skipNonWhite(c)
char *c;
{
    while (c != 0 && *c != 0 && ! isspace(*c)) c++;
    return(c);
}

#define MAX_POINTS 128
static int pointListCount = 0;
static struct pr_pos pointList[MAX_POINTS];

static struct pr_brush specialBrush = {1};

void
applicationDoSpecial(command)
char *command;
{
    char com[20];
    int x,y;
    char *c, *oc;

    c = skipWhite(command);
    if (*c == 0) return;

    oc = c;
    c = skipNonWhite(c);
    x = *c;
    *c = '\000';
    strncpy(com, oc, 20);
    *c = x;
    c++;

    if (strcmp(com, "pa") == 0) {
	sscanf(c, " %d %d ", &x, &y);
	pointList[pointListCount].x =
	    (specialConv * x + dviHH) / shrinkFactor[currentShrink];
	pointList[pointListCount].y =
	    (specialConv * y + dviVV) / shrinkFactor[currentShrink];

	if (! buildToPixmap) {
	    pointList[pointListCount].x += currentLeaf * leaf_w[currentShrink];
	}

	pointListCount++;

/*
 *	Make all lines styles come out as a simple line.
 *	Crude, but effective.
 */

    } else if (strcmp(com, "fp") == 0
	       || strcmp(com,"da") == 0
	       || strcmp(com,"dt") == 0) {
	if (pointListCount > 0) {
	    
	    int fx,fy;
	    int tx,ty;
	    int i;

	    fx = pointList[0].x;
	    fy = pointList[0].y;
	    
	    for (i = 0; i < pointListCount; i++) {
		tx = pointList[i].x;
		ty = pointList[i].y;
		if (buildToPixmap) {
		    pr_vector(pasteUpPixmap[currentShrink][currentLeaf],
			    fx, fy, tx, ty, foregroundMode, -1);
		} else {
		    pw_vector(ptube,
			    fx, fy, tx, ty, foregroundMode, -1);
		}
		fx = tx;
		fy = ty;
	    }
	}
	pointListCount = 0;

    } else if (strcmp(com, "pn") == 0) {
	int penSize;
	sscanf(c," %d ", &penSize);
	penSize /= (shrinkFactor[currentShrink] * 2);	/* looks good */
	penSize = MAXFUNC(penSize,1);
	penSize = MINFUNC(penSize,99);
/*
 *	We dont do anything with this
 */

   } else {
    
/*
 *	For now, lets just flush the special
 */
       pointListCount = 0;
	fprintf(stderr, "[%s] special \"%s\" not implemented\n",
		ProgName, command);
    }
}

ptubeInit()
{
    struct inputmask    mask;

    ptube = canvas_pixwin(ptubeWindow);

    if (ptube == (Pixwin *) NULL) {
	fprintf(stderr,"Couldn't create ptube pixwin\n");
	stop_output(0);
    }

    obscured = 1;	/* SunTools does auto-save-unders */
    globalDisplayState = SHRINK_NORMAL;

    resizeCanvas(ptube,
		 (int) window_get(ptube, CANVAS_WIDTH),
		 (int) window_get(ptube, CANVAS_HEIGHT));

    allocatePixmaps();
    dviCurrentPage = firstPage;
    buildLeaf(LEAF_LEFT, dviCurrentPage, SHRINK_NORMAL);

    if (Leaves == 2) {
	buildLeaf(LEAF_RIGHT, dviCurrentPage + 1, SHRINK_NORMAL);
    }
    globalArg = 0;
    globalNumber = 0;
}

/* respond to damage to ptube subwindow */
static int
ptubeSighandler(sw)
caddr_t sw;
{
    displayLeaves();
}

static int
ptubeSelected(win, inputEvent, arg)
    Window *win;
    Event *inputEvent;
    caddr_t arg;
{
    int ch;
    
    int repaint;
    int rebuild;
    
#define BOGUSDIR -1
#define FOREWARD 0
#define BACKWARD 1
#define ABSOLUTE 2
    
    int direction;
    
    Event *ie;
    
    repaint = 1;
    rebuild = 0;
    direction = BOGUSDIR;

    ie = inputEvent;
    
    switch(event_id(ie)) {
	
    case BUT(1) :
    case BUT(2) :
    case BUT(3):
	if (event_is_down(ie)) {	/* button going down? */
	    int leaf;
	    int normalLeafW = leaf_w[SHRINK_NORMAL];
	    int leafX;
	    int leafY;
	    int lastLeaf = -1;
	    
	    int x = event_x(ie);
	    int y = event_y(ie);
	    
/*
 *	Determine which leaf was pointed to & where we will display it
 */
	    leaf = x / normalLeafW;
	    if (leaf < 0 || leaf >= Leaves) {
		leaf = Leaves - 1;
	    }
	    
	    lastLeaf = leaf;
	    
	    if (Leaves == 1) {
		globalOtherLeaf = leaf;
	    } else {
		globalOtherLeaf = 1 - leaf;
	    }
	    
/*
 *	Build the enlarged page if its not already there
 */
	    if (haveLargePage[leaf] != pageOnLeaf[leaf]) {
		buildLeaf(leaf, pageOnLeaf[leaf], SHRINK_LARGE);
		haveLargePage[leaf] = pageOnLeaf[leaf];
	    }

	    leafX = (x - (leaf * normalLeafW));
	    leafX = (leafX * shrinkFactor[SHRINK_NORMAL])
		/ shrinkFactor[SHRINK_LARGE];

	    leafY = (y * shrinkFactor[SHRINK_NORMAL])
		/ shrinkFactor[SHRINK_LARGE];
	    
	    globalLeafX = leafX;
	    globalLeafY = leafY;
	    globalDisplayLeaf = leaf;
	    globalDisplayState = SHRINK_LARGE;
	    displayLeaves();
	} else {
	    globalDisplayState = SHRINK_NORMAL;
	    displayLeaves();
	}
	return;
	
    case 'q':
    case '\003':	/* control-C */
    case '\004':	/* control-D */
	stop_output(0);
	break;
	
    case 'n':
    case 'f':
    case ' ':
    case '\n' :
    case '\r' :
	/* scroll forward */
	direction = FOREWARD;
	rebuild = 1;
	break;
	
    case 'p':
    case 'b':
    case '\b':
    case 0177 : /* DEL */
	/* scroll backward */
	direction = BACKWARD;
	rebuild = 1;
	break;
	
    case '\f':
    case 'r' :
	/* repaint current page */
	repaint = 1;
	break;
	
    case 'g':
	/* go to absolute page */
	direction = ABSOLUTE; /* may not be, but tough */
	rebuild = 1;
	break;
	
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
	if (! globalArg) {
	    globalArg = 1;
	    globalSign = 1;
	    globalNumber = 0;
	}
	globalNumber = 10*globalNumber + (event_id(ie) - '0');
	return;
	
    case '-':
	if (! globalArg) {
	    globalArg = 1;
	    globalSign = -1;
	    globalNumber = 0;
	    repaint = 0;
	    return;
	} else
	    goto bad;
    default:
	goto bad;
    }
    
    if (rebuild && direction != BOGUSDIR) {
	int newLeft;
	int newRight;
/*
 *	canonicalize the request. globalArg is always > 0, were moving in
 *	some direction.
 */
	
	if (globalArg  == 0 || globalNumber == 0) {
	    globalNumber = 1;
	}

	globalNumber = globalNumber * globalSign;
	
	if (globalNumber < 0) {
	    if (direction == FOREWARD) {
		direction = BACKWARD;
	    } else if (direction == BACKWARD){
		direction = FOREWARD;
	    }
	    globalNumber = -globalNumber;
	}

/* 
 *	Turn pages 
 */
	if (direction == BACKWARD) {
	    newLeft = pageOnLeaf[LEAF_LEFT] - globalNumber;
	} else if (direction == FOREWARD) {
	    newLeft = pageOnLeaf[LEAF_LEFT] + globalNumber;
	} else {
	    newLeft = globalNumber - 1;	/* pages globalNumbered at 0 */
	}
	
	newLeft = MINFUNC(newLeft, dviTotalPages-1);
	newLeft = MAXFUNC(newLeft, 0);
	
	if (Leaves == 1) {
	    if (newLeft != pageOnLeaf[LEAF_LEFT]) {
		buildLeaf(LEAF_LEFT, newLeft, SHRINK_NORMAL);
	    }
	} else {
	    newRight = newLeft + 1;
	    
	    if (pageOnLeaf[LEAF_LEFT] != newLeft) {
		if (newLeft == pageOnLeaf[LEAF_RIGHT]) {
		    swapLeaf(LEAF_RIGHT, LEAF_LEFT);
		} else if (newRight == pageOnLeaf[LEAF_LEFT]) {
		    swapLeaf(LEAF_LEFT, LEAF_RIGHT);
		}
		
		if (pageOnLeaf[LEAF_LEFT] != newLeft) {
		    buildLeaf(LEAF_LEFT, newLeft, SHRINK_NORMAL);
		}
		
		if (pageOnLeaf[LEAF_RIGHT] != newRight) {
		    buildLeaf(LEAF_RIGHT, newRight, SHRINK_NORMAL);
		}
	    }
	}
/*
 *	Only repaint it if we need to
 */
    }
    
    displayLeaves();

    globalArg = 0;
    globalNumber = 0;
    
    return;
    
 bad:
    
    globalArg = 0;		/* throw away numeric globalArgument */
    return;
    
}

put_border(x, y, w, h, t)
int x, y, w, h, t;
{
    put_rectangle(x, y, w, t, setMode);
    put_rectangle(x, y, t, h, setMode);
    put_rectangle(x, y + h - t, w, t, setMode);
    put_rectangle(x + w - t, y, t, h, setMode);
}

put_rectangle(x, y, w, h, pix)
int x, y, w, h;
int pix;
{
    if (buildToPixmap) {
	pr_rop(pasteUpPixmap[currentShrink][currentLeaf],
	       x,y,
	       w,h,
	       pix,
	       pasteUpPixmap[currentShrink][currentLeaf],
	       0, 0);
    } else {
	pw_write(ptube,
		 x + currentLeaf * leaf_w[currentShrink],y,
		 w,h,
		 pix,
		 ptube, 0, 0);
    }
}