/*
 * image.c --
 *
 *      This file implements Tk images of type "dvi". Most of this is
 *      straight boilerplate from tkImgBmap.c.
 *
 * Copyright © 1999 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>
 * See file COPYING for conditions on use and distribution.
 */

#if ENABLE_TK

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "tk.h"
#include "dvi.h"
#include "dviInt.h"

#ifndef lint
static char rcsid[] VAR_UNUSED = "$Id: image.c,v 1.12 2000/07/07 08:00:36 lingnau Exp $";
#endif /* lint */

#if (TK_MAJOR_VERSION > 8) || ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 2))
#define WITH_TKIMAGEPOSTSCRIPTPROC 1
#else
#define WITH_TKIMAGEPOSTSCRIPTPROC 0
#endif

/*
 * It's all right for us to set this here since the headers don't depend
 * on it in Tk <= 8.2.
 */

#if (TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION <= 2)
#define USE_OLD_IMAGE 1
#endif

#ifndef MICKEY_MOUSE
#define MICKEY_MOUSE 0		/* Use simplified rendering for tests */
#endif

/*
 * The following structure represents the master for a dvi image:
 */

typedef struct DviMaster {
    Tk_ImageMaster tkMaster;	/* Tk token for image master */
    Tcl_Interp *interp;		/* Interpreter for application using image */
    Tcl_Command imageCmd;	/* Tk token for image command */
    char *size;			/* Size specification */
    int width, height;		/* Dimensions of image */
    int originX, originY;	/* Coordinates of DVI origin within image.
				 * We need this so width and height are
				 * meaningful -- they are measured from the
				 * upper left corner of the `paper' rather
				 * than the DVI origin. */
    char *originXStr;
    char *originYStr;
    Dvi_Interp *dviInterp;	/* DVI interpreter for the image */
    int loadFonts;
    unsigned int resolutionX;	/* DVI interpreter resolution (shadowed  */
    unsigned int resolutionY;	/* for configuration by widget code) */
    unsigned int maxDrift;	/* DVI interpreter rounding constant */
    unsigned int shrink;	/* Shrink factor for DVI display */
    int render;			/* whether page should be rendered at all */
    int layer;
    int currLayer;
    int maxLayer;
    int grey;			/* whether to do font antialiasing */
    double gamma;		/* Antialiasing display correction factor */
    char *specialCommand;	/* Command to be executed for \special{}s */
    char *preCommand;		/* Command executed before moving to page */
    char *postCommand;		/* Command executed before leaving page */
    unsigned int preCommandMax;	/* Maximum length of cmd after substitution */
    unsigned int postCommandMax; /* Maximum length of cmd after substitution */
    int validatePageNum;	/* whether requests for non-existent pages
				   are errors */
    unsigned int pageNum;	/* Current (absolute) page number */
    unsigned long generation;	/* Incremented when file is reloaded */
    Tk_Uid fileString;		/* DVI file cookie for the image */
    Tk_Uid fgUid;		/* Value of -foreground option (malloc'ed) */
    Tk_Uid bgUid;		/* Value of -background option (malloc'ed) */
    struct DviInstance *instancePtr;
				/* First in list of all instances associated
				 * with this master */
} DviMaster;

#define DVI_LEVELS 17
typedef struct ColorContext {
    struct ColorContext *nextPtr;
    Tk_Uid fgUid, bgUid;
    XColor *fg, *bg;
    int grayLevels;
    XColor *pixelTable[DVI_LEVELS+1];
    GC gc;
    GC bgGc;
} ColorContext;

static ColorContext *colorContextListPtr;

/*
 * The following data structure represents all of the instances of an
 * image that lie within a particular window (not that this would make
 * much sense in the context of a DVI viewer, but who knows?)
 */

#define DVI_COLORSTACK_DEFAULT 8

typedef struct DviInstance {
    int refCount;		/* Number of instances that share this
				 * data structure */
    DviMaster *masterPtr;	/* Pointer to master for image */
    Tk_Window tkwin;		/* Window in which instances will be shown */
    ColorContext *colorCachePtr;
    ColorContext **colorStack;
    unsigned int colorStackSize;
    unsigned int colorStackIdx;
    struct DviInstance *nextPtr;
} DviInstance;

/*
 * The type record for DVI images:
 */

#if USE_OLD_IMAGE
static int              DviImgCreate _ANSI_ARGS_((Tcl_Interp *interp,
                            char *name, int argc, char **argv,
                            Tk_ImageType *typePtr, Tk_ImageMaster master,
                            ClientData *clientDataPtr));
#else
static int              DviImgCreate _ANSI_ARGS_((Tcl_Interp *interp,
                            char *name, int argc, Tcl_Obj * CONST objv[],
                            Tk_ImageType *typePtr, Tk_ImageMaster master,
                            ClientData *clientDataPtr));
#endif /* USE_OLD_IMAGE */
static ClientData       DviImgGet _ANSI_ARGS_((Tk_Window tkwin,
                            ClientData clientData));
static void             DviImgDisplay _ANSI_ARGS_((ClientData clientData,
                            Display *display, Drawable drawable, 
                            int imageX, int imageY, int width, int height,
                            int drawableX, int drawableY));
static void             DviImgFree _ANSI_ARGS_((ClientData clientData,
                            Display *display));
static void             DviImgDelete _ANSI_ARGS_((ClientData clientData));

static Tk_ImageType DviImageType = {
    "dvi",			/* name */
    DviImgCreate,		/* createProc */
    DviImgGet,			/* getProc */
    DviImgDisplay,		/* displayProc */
    DviImgFree,			/* freeProc */
    DviImgDelete,		/* deleteProc */
#if WITH_TKIMAGEPOSTSCRIPTPROC
    (Tk_ImagePostscriptProc *)0,
#endif /* WITHTKIMAGEPOSTSCRIPTPROC */
    (Tk_ImageType *)0		/* nextPtr */
};

/*
 * Information used for parsing configuration specs
 */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_UID, "-background", (char *)0, (char *)0, "white",
     Tk_Offset(DviMaster, bgUid), 0},
    {TK_CONFIG_SYNONYM, "-bg", "-background", (char *)0, "", 0, 0},
    {TK_CONFIG_SYNONYM, "-fg", "-foreground", (char *)0, "", 0, 0},
    {TK_CONFIG_UID, "-file", (char *)0, (char *)0, (char *)0,
     Tk_Offset(DviMaster, fileString), TK_CONFIG_NULL_OK},
    {TK_CONFIG_UID, "-foreground", (char *)0, (char *)0, "black",
     Tk_Offset(DviMaster, fgUid), 0},
    {TK_CONFIG_DOUBLE, "-gamma", (char *)0, (char *)0, "1.2",
     Tk_Offset(DviMaster, gamma), 0},
    {TK_CONFIG_SYNONYM, "-gray", "-grey", (char *)0, "", 0, 0},
    {TK_CONFIG_BOOLEAN, "-grey", (char *)0, (char *)0, "true",
     Tk_Offset(DviMaster, grey), 0},
    {TK_CONFIG_INT, "-layer", (char *)0, (char *)0, "0",
     Tk_Offset(DviMaster, layer), 0},
    {TK_CONFIG_INT, "-maxdrift", (char *)0, (char *)0, "2",
     Tk_Offset(DviMaster, maxDrift), 0},
    {TK_CONFIG_STRING, "-originx", (char *)0, (char *)0, "1in",
     Tk_Offset(DviMaster, originXStr), 0},
    {TK_CONFIG_STRING, "-originy", (char *)0, (char *)0, "1in",
     Tk_Offset(DviMaster, originYStr), 0},
    {TK_CONFIG_STRING, "-precommand", (char *)0, (char *)0, (char *)0,
     Tk_Offset(DviMaster, preCommand), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-postcommand", (char *)0, (char *)0, (char *)0,
     Tk_Offset(DviMaster, postCommand), TK_CONFIG_NULL_OK},
    {TK_CONFIG_INT, "-shrink", (char *)0, (char *)0, "1",
     Tk_Offset(DviMaster, shrink), 0},
    {TK_CONFIG_STRING, "-size", (char *)0, (char *)0, "a4",
     Tk_Offset(DviMaster, size), 0},
    {TK_CONFIG_STRING, "-specialcommand", (char *)0, (char *)0, (char *)0,
     Tk_Offset(DviMaster, specialCommand), TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-validatepagenum", (char *)0, (char *)0, "false",
     Tk_Offset(DviMaster, validatePageNum), 0},
    {TK_CONFIG_INT, "-xresolution", (char *)0, (char *)0, "600",
     Tk_Offset(DviMaster, resolutionX), 0},
    {TK_CONFIG_INT, "-yresolution", (char *)0, (char *)0, "600",
     Tk_Offset(DviMaster, resolutionX), 0},
    {TK_CONFIG_END, (char *)0, (char *)0, (char *)0, (char *)0, 0, 0}
};

/*
 * Paper formats:
 */

typedef struct PaperSize {
    char *name;
    char *width;
    char *height;
} PaperSize;

static PaperSize paperSize[] = {
    { "a7", "74mm", "105mm" },
    { "a6", "105mm", "148mm"},
    { "a5", "148mm", "210mm" },
    { "a4", "210mm", "297mm" },
    { "a3", "297mm", "420mm" },
    { "a2", "420mm", "594mm" },
    { "a1", "594mm", "841mm" },
    { "letter", "8.5in", "11in" },
    { "legal", "8.5in", "14in" },
    { "ledger", "17in", "11in" },
    { "tabloid", "11in", "17in" },
    { "a7r", "105mm", "74mm" },
    { "a6r", "148mm", "105mm" },
    { "a5r", "210mm", "148mm" },
    { "a4r", "297mm", "210mm" },
    { "a3r", "420mm", "297mm" },
    { "a2r", "594mm", "420mm" },
    { "a1r", "841mm", "594mm" },
    { "letterr", "11in", "8.5in" },
    { "legalr", "14in", "8.5in" },
    { (char *)0, (char *)0, (char *)0 }
};

/*
 * Prototypes for procedures used only locally in this file:
 */

static int DviImgCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp,
				  int argc, Tcl_Obj * CONST objv[]));
static void DviImgCmdDeletedProc _ANSI_ARGS_((ClientData clientData));
static void DviImgConfigureInstance _ANSI_ARGS_((DviInstance *instancePtr));
static int DviImgConfigureMaster _ANSI_ARGS_((DviMaster *masterPtr, int argc,
					      char **argv, int flags));

static int PreprocessPrePostCmd _ANSI_ARGS_((const char *cmd,
					     unsigned int *lengthPtr));
static int ExecutePrePostCmd _ANSI_ARGS_((DviMaster *, const char *cmd,
					  const unsigned int cmdLength,
					  const unsigned int pageNum));

static void RenderGlyph _ANSI_ARGS_((ClientData clientData,
				     Dvi_Interp *dviInterp, S32 x, S32 y,
				     Dvi_Font *fontPtr, S32 character,
				     S32 *tfmWidthPtr, S32 *pixelWidthPtr));
static int RenderFontDef _ANSI_ARGS_((ClientData, Dvi_Interp *,
				      Dvi_FontList **, S32, U32, U32,
				      U32, size_t, char *, Dvi_FontDefMode));

/*
 * This is a fixed version of the Tk_InitImageArgs function from the
 * Tk core.
 */

static void
InitImageArgs(interp, argc, argvPtr)
    Tcl_Interp *interp;
    int argc;
    char ***argvPtr;
{
    static useNewImage = -1;
    static char **argv = NULL;
 
    if (argv) {
        Tcl_Free((char *) argv);
        argv = NULL;
    }
 
    if (useNewImage < 0) {
        Tcl_CmdInfo cmdInfo;
        if (!Tcl_GetCommandInfo(interp,"image", &cmdInfo)) {
            Tcl_Panic("cannot find the \"image\" command");
        }
        useNewImage = (cmdInfo.isNativeObjectProc == 1);
    }
    if (useNewImage && (argc > 0)) {
        int i;
        argv = (char **)Tcl_Alloc(argc * sizeof(char *));
        for (i = 0; i < argc; i++) {
            argv[i] = Tcl_GetString((Tcl_Obj *)(*argvPtr)[i]);
        }
        *argvPtr = (char **) argv;
    }
}

/*
 * ------------------------------------------------------------------------
 *
 * Dviimg_Init --
 *
 *      This procedure registers the image type with Tk.
 *
 * ------------------------------------------------------------------------
 */

int
Dviimg_Init (interp)
    Tcl_Interp *interp;
{
#if USE_TCL_STUBS
    if (Tcl_InitStubs(interp, "8.1", 0) == 0) {
	return TCL_ERROR;
    }
#endif

#if USE_TK_STUBS
    if (Tk_InitStubs(interp, "8.1", 0) == 0) {
	return TCL_ERROR;
    }
#endif

    if (Tcl_PkgRequire(interp, "Dviinterp", VERSION, 1) == (char *)0) {
	return TCL_ERROR;
    }
    if (Tcl_PkgRequire(interp, "Dvifont", VERSION, 1) == (char *)0) {
	return TCL_ERROR;
    }

    if (Tcl_PkgProvide(interp, "Dviimg", VERSION) != TCL_OK) {
	return TCL_ERROR;
    }

    Tk_CreateImageType(&DviImageType);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 * DviImgCreate --
 *
 *      This procedure is called by the Tk image code to create DVI images.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      The data structure for a new image is allocated.
 *
 * -----------------------------------------------------------------------
 */

static int
DviImgCreate(interp, name, argc, argv, typePtr, master, clientDataPtr)
    Tcl_Interp *interp;         /* Interpreter for application containing
                                 * image. */
    char *name;                 /* Name to use for image. */
    int argc;                   /* Number of arguments. */
#if USE_OLD_IMAGE
    char **argv;                /* Argument strings for options (doesn't
                                 * include image name or type). */
#else
    Tcl_Obj * CONST argv[];	/* Argument objects for options (doesn't
				 * include image name or type). */
#endif
    Tk_ImageType *typePtr;      /* Pointer to our type record (not used). */
    Tk_ImageMaster master;      /* Token for image, to be used by us in
                                 * later callbacks. */
    ClientData *clientDataPtr;  /* Store manager's token for image here;
                                 * it will be returned in later callbacks. */
{
    DviMaster *masterPtr;

    InitImageArgs(interp, argc, (char ***)&argv);

    masterPtr = (DviMaster *)ckalloc(sizeof(DviMaster));
    if (masterPtr == (DviMaster *)0) {
	return TCL_ERROR;
    }

    masterPtr->tkMaster = master;
    masterPtr->interp = interp;
    masterPtr->imageCmd = Tcl_CreateObjCommand(interp, name, DviImgCmd,
					       (ClientData)masterPtr,
					       DviImgCmdDeletedProc);
    masterPtr->size = (char *)0;
    masterPtr->width = masterPtr->height = 0;
    masterPtr->resolutionX = 600;
    masterPtr->resolutionY = 600;
    masterPtr->originXStr = masterPtr->originYStr = (char *)0;
    masterPtr->originX = masterPtr->resolutionX;
    masterPtr->originY = masterPtr->resolutionY;
    masterPtr->fgUid = 0;
    masterPtr->bgUid = 0;
    masterPtr->shrink = 1;
    masterPtr->gamma = 2.0;
    masterPtr->grey = 1;
    masterPtr->layer = 0;
    masterPtr->validatePageNum = 0;
    masterPtr->maxDrift = 2;
    masterPtr->fileString = (char *)0;
    masterPtr->generation = 0;
    masterPtr->specialCommand = (char *)0;
    masterPtr->preCommand = masterPtr->postCommand = (char *)0;
    masterPtr->preCommandMax = masterPtr->postCommandMax = 0;
    masterPtr->instancePtr = (DviInstance *)0;

    masterPtr->dviInterp = Dvi_CreateInterp(interp, masterPtr->resolutionX,
					    masterPtr->resolutionY, 32,
					    25400000, 473628672, 1000);
    if (masterPtr->dviInterp == (Dvi_Interp *)0) {
	DviImgDelete((ClientData)masterPtr);
	return TCL_ERROR;
    }
    masterPtr->loadFonts = 1;

    masterPtr->render = 0;

    if (DviImgConfigureMaster(masterPtr, argc, (char **)argv, 0) != TCL_OK) {
	DviImgDelete((ClientData)masterPtr);
	return TCL_ERROR;
    }
    *clientDataPtr = (ClientData)masterPtr;
    return TCL_OK;
}

static int
PreprocessPrePostCmd (cmd, lengthPtr)
    const char *cmd;
    unsigned int *lengthPtr;
{
    const char *p;
    int cmdLength = 0;
    
    for (p = cmd; *p; p++) {
	if (*p == '%') {
	    switch (*++p) {
	    case 'p':
		cmdLength += 5; break;
	    case '%':
		cmdLength++; break;
	    default:
		return TCL_ERROR;
	    }
	} else {
	    cmdLength++;
	}
    }
    *lengthPtr = cmdLength;
    return TCL_OK;
}

static int
ExecutePrePostCmd (masterPtr, cmd, cmdLength, pageNum)
    DviMaster *masterPtr;
    const char *cmd;
    const unsigned int cmdLength;
    const unsigned int pageNum;
{
    const char *p;
    char *q;
    char *buffer = (char *)ckalloc(cmdLength + 1);
    
    q = buffer;
    for (p = cmd; *p; p++) {
	if (*p == '%') {
	    switch (*++p) {
	    case 'p': q += sprintf(q, "%u", pageNum); break;
	    case '%': *q++ = '%'; break;
	    default:
		fprintf(stderr, "This can't happen (\"%%%c\")\n", *p);
		*q++ = *p;
		break;
	    }
	} else {
	    *q++ = *p;
	}
    }
    *q = '\0';
    /* fprintf(stderr, "command = `%s'\n", buffer); */
    return Tcl_GlobalEval(masterPtr->interp, buffer);
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgConfigureMaster --
 *
 *      This procedure is called when a DVI image is created or
 *      reconfigured.  It processes configuration options and resets
 *      any instances of the image.
 *
 * Results:
 *      A standard Tcl return value.  If TCL_ERROR is returned then
 *      an error message is left in masterPtr->interp->result.
 *
 * Side effects:
 *      Existing instances of the image will be redisplayed to match
 *      the new configuration options.
 *
 *----------------------------------------------------------------------
 */

static int
DviImgConfigureMaster(masterPtr, argc, argv, flags)
    DviMaster *masterPtr;	/* Pointer to data structure describing
                                 * overall DVI image to (reconfigure). */
    int argc;                   /* Number of entries in argv. */
    char **argv;
    int flags;                  /* Flags to pass to Tk_ConfigureWidget,
                                 * such as TK_CONFIG_ARGV_ONLY. */
{
    DviInstance *instancePtr;
    Tk_Uid oldFileString = masterPtr->fileString;
    PaperSize *paperSizePtr;
    int i;

    if (Tk_ConfigureWidget(masterPtr->interp,
			   Tk_MainWindow(masterPtr->interp),
			   configSpecs, argc, argv, (char *)masterPtr,
			   flags) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * Do DVI stuff here.
     */

    if (masterPtr->fileString
	&& Tk_GetUid(masterPtr->fileString) != oldFileString) {
	Dvi_File *dviFile;

	if (oldFileString != 0) {
	    if ((dviFile = Dvi_GetFileByCookie(masterPtr->interp,
					       oldFileString,
					       TCL_LEAVE_ERR_MSG))
		!= (Dvi_File *)0) {
		Dvi_FileClose(dviFile);
	    }
	}
	if (masterPtr->fileString != 0) {
	    if ((dviFile = Dvi_GetFileByCookie(masterPtr->interp,
					       masterPtr->fileString,
					       TCL_LEAVE_ERR_MSG))
		== (Dvi_File *)0) {
		return TCL_ERROR;
	    }
	    if (dviFile) {
		masterPtr->generation = dviFile->infoPtr->generation;
	    } else {
		masterPtr->generation = 0;
	    }
	    Dvi_SetResolution(masterPtr->dviInterp, masterPtr->resolutionX,
			      masterPtr->resolutionY,
			      dviFile->infoPtr->codePtr->num,
			      dviFile->infoPtr->codePtr->den,
			      dviFile->infoPtr->codePtr->mag);
#if !MICKEY_MOUSE
	    masterPtr->dviInterp->fontDefProc = RenderFontDef;
	    masterPtr->loadFonts = 1;
#endif /* MICKEY_MOUSE */
	} else {
	    dviFile = (Dvi_File *)0;
	}
    } else {
	Dvi_SetResolution(masterPtr->dviInterp, masterPtr->resolutionX,
			  masterPtr->resolutionY, 25400000, 473628672, 1000);
    }

    /*
     * Figure out the image size. This is mostly cosmetic, so we can set
     * up canvas scroll regions etc.; the DVI code does not depend on knowing
     * the image size.
     */

    if (masterPtr->size == (char *)0) {
	Tcl_SetResult(masterPtr->interp, "No image size specified",TCL_STATIC);
	return TCL_ERROR;
    }

    /*
     * Go through the list of predefined paper sizes to see whether one fits.
     * If so, set image width and height appropriately.
     */

    for (paperSizePtr = paperSize;
	 paperSizePtr->name && strcmp(masterPtr->size, paperSizePtr->name);
	 paperSizePtr++)
	;

    if (paperSizePtr->name) {
	if (Dvi_GetPixels(masterPtr->interp, masterPtr->resolutionX,
			  paperSizePtr->width, &masterPtr->width, 0) != TCL_OK
	    || Dvi_GetPixels(masterPtr->interp, masterPtr->resolutionY,
			     paperSizePtr->height, &masterPtr->height, 0)
	    != TCL_OK) {
	    Tcl_SetResult(masterPtr->interp, "bad internal paper size",
			  TCL_STATIC);
	}
    } else {
	/*
	 * No predefined paper size specified. Check for pair of dimensions.
	 */
	int dimenc;
	char **dimenv;

	if (Tcl_SplitList(masterPtr->interp, masterPtr->size,
			  &dimenc, &dimenv) != TCL_OK) {
	    return TCL_ERROR;
	}

	if (dimenc != 2) {
	    Tcl_Free((char *)dimenv);
	    Tcl_SetResult(masterPtr->interp,
			  "paper size must consist of two dimensions",
			  TCL_STATIC);
	    return TCL_ERROR;
	}

	if (Dvi_GetPixels(masterPtr->interp, masterPtr->resolutionX,
			  dimenv[0], &masterPtr->width, 0) != TCL_OK) {
	    Tcl_Free((char *)dimenv);
	    Tcl_SetResult(masterPtr->interp,
			  "Image has invalid width", TCL_STATIC);
	    return TCL_ERROR;
	}
	if (Dvi_GetPixels(masterPtr->interp, masterPtr->resolutionY,
			  dimenv[1], &masterPtr->height, 0) != TCL_OK) {
	    Tcl_Free((char *)dimenv);
	    Tcl_SetResult(masterPtr->interp,
			  "Image has invalid height", TCL_STATIC);
	    return TCL_ERROR;
	}
	Tcl_Free((char *)dimenv);
    }

    /*
     * Apply shrink factor.
     */

    masterPtr->width /= masterPtr->shrink;
    masterPtr->height /= masterPtr->shrink;

    if (masterPtr->width <= 0) {
	Tcl_SetResult(masterPtr->interp, "Image has invalid width",TCL_STATIC);
	return TCL_ERROR;
    }
    if (masterPtr->height <= 0) {
	Tcl_SetResult(masterPtr->interp,"Image has invalid height",TCL_STATIC);
	return TCL_ERROR;
    }

    /*
     * Figure out DVI coordinate origin.
     */

    if (masterPtr->originXStr == (char *)0
	|| masterPtr->originYStr == (char *)0) {
	Tcl_SetResult(masterPtr->interp,
		      "Image has invalid DVI origin specification",TCL_STATIC);
	return TCL_ERROR;
    }
    if (Dvi_GetPixels(masterPtr->interp, masterPtr->resolutionX,
		      masterPtr->originXStr, &masterPtr->originX, 0)
	!= TCL_OK) {
	Tcl_SetResult(masterPtr->interp,
	      "Image has invalid DVI origin x specification", TCL_STATIC);
	return TCL_ERROR;
    }
    if (Dvi_GetPixels(masterPtr->interp, masterPtr->resolutionY,
		      masterPtr->originYStr, &masterPtr->originY, 0)
	!= TCL_OK) {
	Tcl_SetResult(masterPtr->interp,
	      "Image has invalid DVI origin y specification", TCL_STATIC);
	return TCL_ERROR;
    }

    /*
     * Preprocess preCommand and postCommand
     */

    if (masterPtr->preCommand != 0) {
	if (PreprocessPrePostCmd(masterPtr->preCommand,
				 &masterPtr->preCommandMax) != TCL_OK) {
	    Tcl_SetResult(masterPtr->interp,
			  "Invalid substitution in -precommand", TCL_STATIC);
	}
    }

    if (masterPtr->postCommand != 0) {
	if (PreprocessPrePostCmd(masterPtr->postCommand,
				 &masterPtr->postCommandMax) != TCL_OK) {
	    Tcl_SetResult(masterPtr->interp,
			  "Invalid substitution in -postcommand", TCL_STATIC);
	}
    }

    /*
     * Cycle through all of the instances of this image, regenerating
     * the information for each instance.  Then force the image to be
     * redisplayed everywhere that it is used.
     */

    for (instancePtr = masterPtr->instancePtr; instancePtr != NULL;
            instancePtr = instancePtr->nextPtr) {
        DviImgConfigureInstance(instancePtr);
    }
    Tk_ImageChanged(masterPtr->tkMaster, 0, 0, masterPtr->width,
            masterPtr->height, masterPtr->width, masterPtr->height);
    return TCL_OK;
}

static int
InitPixelTable (instancePtr, colorContextPtr)
    DviInstance *instancePtr;
    ColorContext *colorContextPtr;
{
    XColor fg, bg;
    int i;
    int levels =
	instancePtr->masterPtr->shrink * instancePtr->masterPtr->shrink + 1;
    Tk_Window tkwin = instancePtr->tkwin;

    if (levels <= 2) {
	colorContextPtr->grayLevels = levels;
	return TCL_OK;
    }

    if (levels > DVI_LEVELS) {
	levels = DVI_LEVELS;
    }

    fg.pixel = colorContextPtr->fg->pixel;
    XQueryColor(Tk_Display(tkwin), Tk_Colormap(tkwin), &fg);
    bg.pixel = colorContextPtr->bg->pixel;
    XQueryColor(Tk_Display(tkwin), Tk_Colormap(tkwin), &bg);

    /*
     * We have an extra entry at the end of the pixelTable to cater
     * for rounding errors during the sample calculation. This uses
     * a bit more memory but saves us extra operations in the inner
     * loop of the anti-aliaser, where it counts.
     */

    colorContextPtr->pixelTable[0] = colorContextPtr->bg;
    colorContextPtr->pixelTable[levels-1] = colorContextPtr->fg;
    colorContextPtr->pixelTable[levels] = colorContextPtr->fg;
    for (i = 1; i < levels - 1; i++) {
	XColor pref;
	double f = pow((double)i/(levels-1),
		       1.0/instancePtr->masterPtr->gamma);

	pref.red = f * ((double)fg.red - (double)bg.red) + bg.red;
	pref.green = f * ((double)fg.green - (double)bg.green) + bg.green;
	pref.blue = f * ((double)fg.blue - (double)bg.blue) + bg.blue;

	colorContextPtr->pixelTable[i] = Tk_GetColorByValue(tkwin, &pref);
    }

    colorContextPtr->grayLevels = levels;
    return TCL_OK;
}

static ColorContext *
FindColorContext (instancePtr, fgUid, bgUid)
    DviInstance *instancePtr;
    Tk_Uid fgUid, bgUid;
{
    ColorContext *colorContextPtr;
    XGCValues gcValues;
    unsigned int mask = 0;

    /*
     * Check whether we have an appropriate color context in the cache.
     * If so, return a pointer to it.
     */

    for (colorContextPtr = instancePtr->colorCachePtr;
	 colorContextPtr
	     && (fgUid != colorContextPtr->fgUid
		 || bgUid != colorContextPtr->bgUid);
	 colorContextPtr = colorContextPtr->nextPtr)
	;
    if (colorContextPtr) {
	return colorContextPtr;
    }

    /*
     * Create the color context.
     */

    colorContextPtr = (ColorContext *)ckalloc(sizeof(ColorContext));
    if (colorContextPtr == (ColorContext *)0) {
	return (ColorContext *)0;
    }

    colorContextPtr->fgUid = fgUid;
    colorContextPtr->fg = Tk_GetColor(instancePtr->masterPtr->interp,
				      instancePtr->tkwin, fgUid);
    if (colorContextPtr->fg == NULL) {
	ckfree((char *)colorContextPtr);
	return (ColorContext *)0;
    }

    colorContextPtr->bgUid = bgUid;
    colorContextPtr->bg = Tk_GetColor(instancePtr->masterPtr->interp,
				      instancePtr->tkwin, bgUid);
    if (colorContextPtr->bg == NULL) {
	ckfree((char *)colorContextPtr);
	Tk_FreeColor(colorContextPtr->fg);
	return (ColorContext *)0;
    }

    InitPixelTable(instancePtr, colorContextPtr);

    gcValues.foreground = colorContextPtr->fg->pixel;
    gcValues.background = colorContextPtr->bg->pixel;
    gcValues.graphics_exposures = False;
#if 0
    gcValues.function = GXcopyInverted;
    mask = GCForeground | GCBackground | GCGraphicsExposures | GCFunction;
#else
    mask = GCForeground | GCBackground | GCGraphicsExposures;
#endif
    colorContextPtr->gc = Tk_GetGC(instancePtr->tkwin, mask, &gcValues);

    gcValues.foreground = colorContextPtr->bg->pixel;
    mask = GCForeground | GCGraphicsExposures;
    colorContextPtr->bgGc = Tk_GetGC(instancePtr->tkwin, mask, &gcValues);

    colorContextPtr->nextPtr = instancePtr->colorCachePtr;
    instancePtr->colorCachePtr = colorContextPtr;

    return colorContextPtr;
}

static void
PushFgColor (instancePtr, colorSpec, length)
    DviInstance *instancePtr;
    const char *colorSpec;
    size_t length;
{
    char buffer[128];
    char *p;
    Tk_Uid fgUid, bgUid;
    ColorContext *colorContextPtr;

    /* fprintf(stderr, "PushFgColor: instancePtr=%p length=%lu colorSpec=`%*.*s'\n", instancePtr, length, (int)length, (int)length, colorSpec); */
    if (length >= sizeof(buffer) - 1) {
	fprintf(stderr, "PushFgColor: colorSpec too long\n");
	return;
    }

    while (length && isspace(*colorSpec)) {
	colorSpec++;
	length--;
    }

    if (length == 0) {
	return;
    }

    memset(buffer, 0, sizeof(buffer));
    strncpy(buffer, colorSpec, length);

    p = buffer;
    while (*p && !isspace(*p)) {
	++p;
    }
    *p++ = '\0';

    if (strcmp(buffer, "rgb") == 0) {
	float r, g, b;
	/* fprintf(stderr, "PushFgColor: ... RGB color %s\n", p); */
	if (sscanf(p, "%f %f %f", &r, &g, &b) != 3
	    || r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) {
	    fprintf(stderr, "PushFgColor: Invalid RGB color `%s'\n", p);
	    return;
	}
	sprintf(buffer, "#%02x%02x%02x", (unsigned int)(255*r),
		(unsigned int)(255*g), (unsigned int)(255*b));
    } else if (strcmp(buffer, "cmyk") == 0) {
	float c, m, y, k, r, g, b;
	/* fprintf(stderr, "PushFgColor: ... CMYK color %s\n", p); */
	if (sscanf(p, "%f %f %f %f", &c, &m, &y, &k) != 4) {
	    fprintf(stderr, "PushFgColor: Invalid CMYK color `%s'\n", p);
	    return;
	}
	c += k, m += k, y += k;
	r = 1 - c, g = 1 - m, b = 1 - y;
	if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) {
	    fprintf(stderr, "PushFgColor: Invalid CMYK color `%s'\n", p);
	}
	sprintf(buffer, "#%02x%02x%02x", (unsigned int)(255*r),
		(unsigned int)(255*g), (unsigned int)(255*b));
    } else {
	/* fprintf(stderr, "PushFgColor: ... Spot color %s\n", buffer); */
    }
    fgUid = Tk_GetUid(buffer);

    /*
     * This isn't working at all correctly. Please disregard or fix.
     */
#if 0
    bgUid = instancePtr->colorStackIdx == 0
	? instancePtr->colorStack[instancePtr->colorStackIdx]->bgUid
	: instancePtr->colorStack[instancePtr->colorStackIdx]->fgUid;
#else
    bgUid = instancePtr->colorStack[instancePtr->colorStackIdx]->bgUid;
#endif

    colorContextPtr = FindColorContext(instancePtr, fgUid, bgUid);

    if (colorContextPtr == (ColorContext *)0) {
	fprintf(stderr, "PushFgColor: Couldn't get color context\n");
	/* Make sure we have something to pop later */
	colorContextPtr = instancePtr->colorStack[instancePtr->colorStackIdx];
    }
    instancePtr->colorStack[++instancePtr->colorStackIdx] = colorContextPtr;
}

static void
PopFgColor (instancePtr)
    DviInstance *instancePtr;
{
    /* fprintf(stderr, "PopFgColor\n"); */
    if (instancePtr->colorStackIdx > 0) {
	instancePtr->colorStackIdx--;
    }
}

static void
FreeColorContextCache (instancePtr)
    DviInstance *instancePtr;
{
    ColorContext *colorContextPtr;
    ColorContext *oldPtr;
    int i;

    colorContextPtr = instancePtr->colorCachePtr;
    while (colorContextPtr) {
	oldPtr = colorContextPtr;
	colorContextPtr = colorContextPtr->nextPtr;
	Tk_FreeColor(oldPtr->fg);
	Tk_FreeColor(oldPtr->bg);
	if (oldPtr->grayLevels > 2) {
	    for (i = 1; i < oldPtr->grayLevels - 1; i++) {
		Tk_FreeColor(oldPtr->pixelTable[i]);
	    }
	}
	Tk_FreeGC(Tk_Display(instancePtr->tkwin), oldPtr->gc);
	Tk_FreeGC(Tk_Display(instancePtr->tkwin), oldPtr->bgGc);
	ckfree((char *)oldPtr);
    }
    instancePtr->colorCachePtr = 0;
    instancePtr->colorStackIdx = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgConfigureInstance --
 *
 *      This procedure is called to create displaying information for
 *      a DVI image instance based on the configuration information
 *      in the master.  It is invoked both when new instances are
 *      created and when the master is reconfigured.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Generates errors via Tk_BackgroundError if there are problems
 *      in setting up the instance.
 *
 *----------------------------------------------------------------------
 */

static void
DviImgConfigureInstance(instancePtr)
    DviInstance *instancePtr;	/* Instance to reconfigure. */
{
    DviMaster *masterPtr = instancePtr->masterPtr;
    ColorContext *colorContextPtr;

    /*
     * For each of the options in masterPtr, translate the string
     * form into an internal form appropriate for instancePtr.
     */

    if (*masterPtr->bgUid != 0 && *masterPtr->fgUid != 0) {
	FreeColorContextCache(instancePtr);
	/* fprintf(stderr, "DviImgConfigureInstance: instancePtr=%p, masterPtr->bgUid=%s, fgUid=%s\n", instancePtr, masterPtr->bgUid, masterPtr->fgUid); */
	colorContextPtr = FindColorContext(instancePtr, masterPtr->fgUid,
					   masterPtr->bgUid);
	if (colorContextPtr == 0) {
	    goto error;
	}
	instancePtr->colorCachePtr = colorContextPtr;
	instancePtr->colorStackIdx = 0;
	instancePtr->colorStack[instancePtr->colorStackIdx] = colorContextPtr;
    }

    /* fprintf(stderr, "DviImgConfigureInstance: done\n"); */

    return;

 error:
    /*
     * An error occurred: clear the graphics context in the instance to
     * make it clear that this instance cannot be displayed.  Then report
     * the error.
     */

#if 0
    if (instancePtr->gc != None) {
        Tk_FreeGC(Tk_Display(instancePtr->tkwin), instancePtr->gc);
    }
    if (instancePtr->bgGc != None) {
	Tk_FreeGC(Tk_Display(instancePtr->tkwin), instancePtr->bgGc);
    }
    instancePtr->gc = instancePtr->bgGc = None;
#endif

    Tcl_AddErrorInfo(masterPtr->interp, "\n    (while configuring image \"");
    Tcl_AddErrorInfo(masterPtr->interp, Tk_NameOfImage(masterPtr->tkMaster));
    Tcl_AddErrorInfo(masterPtr->interp, "\")");
    Tk_BackgroundError(masterPtr->interp);
}

/*
 *--------------------------------------------------------------
 *
 * DviImgCmd --
 *
 *      This procedure is invoked to process the Tcl command
 *      that corresponds to an image managed by this module.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int
DviImgCmd(clientData, interp, objc, objv)
    ClientData clientData;      /* Information about DVI image. */
    Tcl_Interp *interp;         /* Current interpreter. */
    int objc;                   /* Number of arguments. */
    Tcl_Obj * CONST objv[];
{
    DviMaster *masterPtr = (DviMaster *)clientData;
    int c, code;
    size_t length;
    Dvi_File *dviFile;
    Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);

    static enum {
	DVII_CGET, DVII_CONFIGURE, DVII_DEFSIZE, DVII_FONTS, DVII_LAYERS,
	DVII_PAGE, DVII_SIZE,
    } idx;
    static char *subCmds[] = {
	"cget", "configure", "defsize", "fonts", "layers",
	"page", "size",
	(char *)0
    };

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?parameters?");
        return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[1], subCmds, "subcommand",
			    TCL_EXACT, (int *)&idx) != TCL_OK) {
	return TCL_ERROR;
    }

    switch (idx) {
	Dvi_FontList *listPtr;

    case DVII_CGET:
        if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "option");
            return TCL_ERROR;
        }
        return Tk_ConfigureValue(interp, Tk_MainWindow(interp), configSpecs,
				 (char *)masterPtr,
				 Tcl_GetStringFromObj(objv[2], 0), 0);

    case DVII_CONFIGURE:
	if (objc == 2) {
	    code = Tk_ConfigureInfo(interp, Tk_MainWindow(interp), configSpecs,
				    (char *)masterPtr, (char *)0, 0);
	} else if (objc == 3) {
            code = Tk_ConfigureInfo(interp, Tk_MainWindow(interp), configSpecs,
				    (char *)masterPtr,
				    Tcl_GetStringFromObj(objv[2], 0), 0);
        } else {
	    int i;
	    char **argv = (char **)ckalloc((objc-2+1) * sizeof(char *));
	    for (i = 0; i < objc - 2; i++) {
		argv[i] = Tcl_GetStringFromObj(objv[i+2], 0);
	    }
	    argv[objc-2] = 0;
            code = DviImgConfigureMaster(masterPtr, objc-2, argv,
					 TK_CONFIG_ARGV_ONLY);
	    ckfree((char *)argv);
        }
        return code;

    case DVII_DEFSIZE:
	if (objc == 2) {
	    PaperSize *paperSizePtr;
	    Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);

	    for (paperSizePtr=paperSize; paperSizePtr->name; paperSizePtr++) {
		if (Tcl_ListObjAppendElement(interp, resultPtr,
		       Tcl_NewStringObj(paperSizePtr->name, -1)) != TCL_OK) {
		    return TCL_ERROR;
		}
	    }
	    return TCL_OK;
	} else if (objc == 3) {
	    PaperSize *paperSizePtr;
	    char *paperName = Tcl_GetStringFromObj(objv[2], 0);
	    Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);

	    for (paperSizePtr = paperSize;
		 paperSizePtr->name && strcmp(paperSizePtr->name,paperName)!=0;
		 paperSizePtr++)
		;
	    if (paperSizePtr->name == (char *)0) {
		Tcl_AppendStringsToObj(resultPtr, "page size \"", paperName,
				       "\" not defined", (char *)0);
		return TCL_ERROR;
	    }
	    Tcl_AppendStringsToObj(resultPtr, paperSizePtr->width,
				   paperSizePtr->height, (char *)0);
	    return TCL_OK;
	} else if (objc == 5) {
	    Tcl_SetResult(interp, "Not implemented yet", TCL_STATIC);
	    return TCL_ERROR;
	} else {
	    Tcl_WrongNumArgs(interp, 2, objv, "?papersize? ?width height?"); 
	    return TCL_ERROR;
	}
	break;

    case DVII_LAYERS:
	if (objc == 2) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(masterPtr->maxLayer + 1));
	    return TCL_OK;
	} else {
            Tcl_WrongNumArgs(interp, 2, objv, "");
	    return TCL_ERROR;
	}
	break;

    case DVII_FONTS:
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, "");
	    return TCL_ERROR;
	}

	for (listPtr = masterPtr->dviInterp->fonts;
	     listPtr;
	     listPtr = listPtr->nextPtr) {
	    Tcl_Obj *item[2];
	    item[0] = Tcl_NewLongObj((long)listPtr->fontNum);
	    item[1] = Dvi_FontDump(interp, listPtr->fontPtr);
	    Tcl_ListObjAppendElement(interp, resultPtr,
				     Tcl_NewListObj(2, item));
	}
	break;

    case DVII_PAGE:
	if (objc == 2) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(masterPtr->pageNum));
	    return TCL_OK;
	} else if (objc == 3) {
	    Dvi_PageSpec pageSpec;
	    char *pageSpecStr;
	    int pageNum;
	    U8 *dviCodePtr;
	    int oldRender = masterPtr->render;

	    pageSpecStr = Tcl_GetStringFromObj(objv[2], 0);
	    if (Dvi_CodeGetPageSpec(pageSpecStr, &pageSpec) == -1) {
		Tcl_AppendStringsToObj(resultPtr,
				       "invalid page specification \"",
				       pageSpecStr, "\"", (char *)0);
		return TCL_ERROR;
	    }
	    masterPtr->render = 1;
	    if ((dviFile = Dvi_GetFileByCookie(masterPtr->interp,
					       masterPtr->fileString,
					       TCL_LEAVE_ERR_MSG))
		== (Dvi_File *)0) {
		return TCL_ERROR;
	    }

	    if (dviFile->infoPtr->generation > masterPtr->generation) {
		Dvi_ResetInterp(masterPtr->dviInterp, 1);
		if (Dvi_FontsFromPostamble(masterPtr->dviInterp,
					   dviFile) != TCL_OK) {
		    masterPtr->render = 0;
		    Tcl_SetResult(masterPtr->interp,
				  "couldn't re-read fonts", TCL_STATIC);
		    return TCL_ERROR;
		}
	    }

	    pageNum = Dvi_CodeFindTeXPage(dviFile->infoPtr->codePtr,
					  &pageSpec);
	    if (pageNum < 0) {
		if (masterPtr->validatePageNum) {
		    Tcl_AppendStringsToObj(resultPtr,
					   "Couldn't find page \"",
					   pageSpecStr, "\"", (char *)0);
		    return TCL_ERROR;
		} else {
		    masterPtr->render = 0;
		}
	    }

	    if (oldRender && masterPtr->postCommand) {
		ExecutePrePostCmd(masterPtr, masterPtr->postCommand,
				  masterPtr->postCommandMax,
				  masterPtr->pageNum);
	    }

	    if (masterPtr->render && masterPtr->preCommand) {
		ExecutePrePostCmd(masterPtr, masterPtr->preCommand,
				  masterPtr->preCommandMax, pageNum);
	    }

	    masterPtr->pageNum = pageNum;
	    masterPtr->maxLayer = -1;
	    Tk_ImageChanged(masterPtr->tkMaster, 0, 0, masterPtr->width,
			    masterPtr->height, masterPtr->width,
			    masterPtr->height);
	} else {
            Tcl_WrongNumArgs(interp, 2, objv, "?pageSpec?");
	    return TCL_ERROR;
	}
	break;

    case DVII_SIZE:
	if (objc > 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, "");
	    return TCL_ERROR;
	} else {
	    Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);

	    Tcl_AppendObjToObj(resultPtr, Tcl_NewIntObj(masterPtr->width));
	    Tcl_AppendObjToObj(resultPtr, Tcl_NewIntObj(masterPtr->height));
	    return TCL_OK;
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgGet --
 *
 *      This procedure is called for each use of a DVI image in a
 *      widget.
 *
 * Results:
 *      The return value is a token for the instance, which is passed
 *      back to us in calls to DviImgDisplay and DviImgFree.
 *
 * Side effects:
 *      A data structure is set up for the instance (or, an existing
 *      instance is re-used for the new one).
 *
 *----------------------------------------------------------------------
 */

static ClientData
DviImgGet(tkwin, masterData)
    Tk_Window tkwin;            /* Window in which the instance will be
                                 * used. */
    ClientData masterData;      /* Pointer to our master structure for the
                                 * image. */
{
    DviMaster *masterPtr = (DviMaster *)masterData;
    DviInstance *instancePtr;

    /*
     * See if there is already an instance for this window.  If so
     * then just re-use it.
     */

    for (instancePtr = masterPtr->instancePtr; instancePtr != NULL;
            instancePtr = instancePtr->nextPtr) {
        if (instancePtr->tkwin == tkwin) {
            instancePtr->refCount++;
            return (ClientData) instancePtr;
        }
    }

    /*
     * The image isn't already in use in this window.  Make a new
     * instance of the image.
     */

    instancePtr = (DviInstance *)ckalloc(sizeof(DviInstance));
    instancePtr->refCount = 1;
    instancePtr->masterPtr = masterPtr;
    instancePtr->tkwin = tkwin;
    instancePtr->colorCachePtr = (ColorContext *)0;
    instancePtr->colorStack = (ColorContext **)ckalloc(DVI_COLORSTACK_DEFAULT
					     * sizeof(ColorContext *));
    instancePtr->colorStackSize = DVI_COLORSTACK_DEFAULT;
    instancePtr->colorStackIdx = 0;

    instancePtr->nextPtr = masterPtr->instancePtr;
    masterPtr->instancePtr = instancePtr;
    DviImgConfigureInstance(instancePtr);

    /*
     * If this is the first instance, must set the size of the image.
     */

    if (instancePtr->nextPtr == (DviInstance *)0) {
        Tk_ImageChanged(masterPtr->tkMaster, 0, 0, 0, 0, masterPtr->width,
                masterPtr->height);
    }

    return (ClientData)instancePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgDisplay --
 *
 *      This procedure is invoked to draw a DVI image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      A portion of the image gets rendered in a pixmap or window.
 *
 *----------------------------------------------------------------------
 */

typedef struct RenderData {
    DviMaster *masterPtr;
    DviInstance *instancePtr;
    Display *display;
    Drawable drawable;
    int minX, maxX, minY, maxY;
    int drawableX, drawableY;
} RenderData;

static U8 mirrorByte[256] = { 0, 0, };

static XImage *
GlyphToImageOriginal (instancePtr, glyphPtr, dxPtr, dyPtr)
    DviInstance *instancePtr;
    Dvi_Glyph *glyphPtr;
    S32 *dxPtr, *dyPtr;
{
    static XImage *glyphImage = 0;

    if (glyphImage == (XImage *)0) {
	glyphImage = XCreateImage(Tk_Display(instancePtr->tkwin),
				  Tk_Visual(instancePtr->tkwin),
				  1, XYBitmap, 0,
				  0, 0, 0, 8, 0);
    }

    /*
     * This penalizes little-endian machines such as my Intel-based Linux
     * PC but will have to do for the time being.
     */

    if (glyphPtr->renderData == 0) {
	U8 *glyphBitmapPtr = (U8 *)glyphPtr + sizeof(Dvi_Glyph);
	if (glyphImage->bitmap_bit_order == LSBFirst) {
	    int size = glyphPtr->height * glyphPtr->bytesWidth;
	    int i, y;

	    glyphPtr->renderData = ckalloc(size);
	    for (y = 0; y < glyphPtr->height; y++) {
		for (i = 0; i < glyphPtr->bytesWidth; i++) {
		    glyphPtr->renderData[y*glyphPtr->bytesWidth+i]
			= mirrorByte[glyphBitmapPtr[y*glyphPtr->bytesWidth+i]];
		}
	    }
	} else {
	    glyphPtr->renderData = glyphBitmapPtr;
	}
    }

    glyphImage->bytes_per_line = glyphPtr->bytesWidth;
    glyphImage->width = glyphPtr->width;
    glyphImage->height = glyphPtr->height;
    glyphImage->data = (char *)glyphPtr->renderData;

    *dxPtr = glyphPtr->horizOffset;
    *dyPtr = glyphPtr->vertOffset;
    return glyphImage;
}

U8 sampleCount[] = {
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
};
U8 bitMask[] = { 0, 1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff };

static int
Sample (bits, bytesWidth, bitSkip, width, height)
    U8 *bits;
    int bytesWidth;
    int bitSkip;
    int width, height;
{
    U8 *ptr, *endPtr, *cp;
    int bitsLeft;
    int n, bitShift, bitsInByte;
    U8 mask;

    /*
     * ptr points to the first byte considered (top left of region to sample).
     * endPtr points to the byte `height' rows below where ptr points
     * (this is the first byte which is *not* considered)
     * bitsLeft is the number of bits to be considered in one row
     */

    ptr = bits + bitSkip / 8;
    endPtr = ptr + height * bytesWidth;
    bitsLeft = width;

    bitShift = bitSkip % 8;
    n = 0;
    while (bitsLeft) {
	/*
	 * Consider a vertical column of `height' rows below and
	 * including `*ptr'.
	 */
	bitsInByte = 8 - bitShift;
	if (bitsInByte > bitsLeft) {
	    bitsInByte = bitsLeft;
	}
	mask = bitMask[bitsInByte] << (8 - bitShift - bitsInByte);
	for (cp = ptr; cp < endPtr; cp += bytesWidth) {
	    n += sampleCount[*cp & mask];
	}
	bitsLeft -= bitsInByte;
	bitShift = 0;
	ptr++; endPtr++;
    }
    return n;
}   

static XImage *
GlyphToImageShrink (instancePtr, glyphPtr, dxPtr, dyPtr, shrink)
    DviInstance *instancePtr;
    Dvi_Glyph *glyphPtr;
    S32 *dxPtr, *dyPtr;
    int shrink;
{
    int rowsLeft, rows, initCols;
    int colsLeft, cols;
    int refX, refY, width, height;
    int x, y;
    int theSample;
    U8 *oldPtr;
    unsigned int size;
    ColorContext *colorContextPtr
	= instancePtr->colorStack[instancePtr->colorStackIdx];
    int first = 1;

    XImage *glyphImage;

    double pixScale = 1.0;
    if ((shrink*shrink+1.0)/DVI_LEVELS > 1) {
	pixScale = (double)DVI_LEVELS / (shrink * shrink);
    }

    /* fprintf(stderr, "GlyphToImageShrink: %p %p %d\n", instancePtr, glyphPtr, shrink); */

    /*
     * If a shrunken glyph is cached at the correct size, return
     * via the short route.
     */

    if (shrink == glyphPtr->shrink && glyphPtr->shrinkGlyphPtr != 0
	&& colorContextPtr->fgUid == (Tk_Uid)glyphPtr->fgUid
	/* && colorContextPtr->bgUid == glyphPtr->bgUid */) {
	*dxPtr = glyphPtr->hShrinkOffset;
	*dyPtr = glyphPtr->vShrinkOffset;
	return (XImage *)(glyphPtr->shrinkGlyphPtr);
    }

    /*
     * Free any existing shrunken glyph.
     */

    if (glyphPtr->shrink != 0 && glyphPtr->shrinkGlyphPtr != 0) {
	XDestroyImage((XImage *)(glyphPtr->shrinkGlyphPtr));
	glyphPtr->shrinkGlyphPtr = 0;
	glyphPtr->shrink = 0;
    }

    /*
     * Ensure glyph is shrunk according to its reference point.
     * (This code is inspired by xdvi.)
     */

#define RoundUp(k,m) ((((k) + (m)) - 1) / (m))

    refX = glyphPtr->horizOffset / shrink;
    initCols = glyphPtr->horizOffset - refX * shrink;
    if (initCols <= 0) {
	initCols += shrink;
    } else {
	refX++;
    }
    width = refX + RoundUp((int)glyphPtr->width - glyphPtr->horizOffset,
			   shrink) + 1;
    if (width <= 0) {
	width = 1;
    }

    cols = glyphPtr->vertOffset + 1; /* ensure row zero is counted as + */
    refY = cols / shrink;
    rows = cols - refY * shrink;
    if (rows <= 0) {
	rows += shrink;
	--refY;
    }
    height = refY + RoundUp((int)glyphPtr->height - cols, shrink) + 1;
    if (height <= 0) {
	height = 1;
    }

    /* fprintf(stderr, ": gw %d, gho %d, refX %d, refY %d, width %d, height %d => ", glyphPtr->width, glyphPtr->horizOffset, refX, refY, width, height); */

    /*
     * Create an X image if necessary.
     */

    if (glyphPtr->shrinkGlyphPtr == 0) {
	glyphImage = XCreateImage(Tk_Display(instancePtr->tkwin),
				  Tk_Visual(instancePtr->tkwin),
				  Tk_Depth(instancePtr->tkwin),
				  ZPixmap, 0, 0,
				  width, height, 8, 0);
	glyphPtr->shrinkGlyphPtr = glyphImage;
    }

    size = glyphImage->bytes_per_line * (height+2);

    glyphImage->data = ckalloc(size != 0 ? size : 1);

    oldPtr = (U8 *)glyphPtr + sizeof(struct Dvi_Glyph);
    rowsLeft = glyphPtr->height;
    y = 0;
    while (rowsLeft > 0) {
	x = 0;
	if (rows > rowsLeft) {
	    rows = rowsLeft;
	}
	colsLeft = glyphPtr->width;
	cols = initCols;
	while (colsLeft > 0) {
	    if (cols > colsLeft) {
		cols = colsLeft;
	    }
	    theSample = Sample(oldPtr, glyphPtr->bytesWidth,
			       (int)glyphPtr->width - colsLeft, cols, rows)
		* pixScale;
	    XPutPixel(glyphImage, x, y,
		      colorContextPtr->pixelTable[theSample]->pixel);
	    colsLeft -= cols;
	    cols = shrink;
	    x++;
	}
	while (x < glyphImage->width) {
	    /* fprintf(stderr, "0"); */
	    XPutPixel(glyphImage, x, y, colorContextPtr->bg->pixel);
	    x++;
	}
	oldPtr += rows * glyphPtr->bytesWidth;
	rowsLeft -= rows;
	rows = shrink;
	y++;
	/* fprintf(stderr, "\n"); */
    }
    while (y < glyphImage->height) {
	for (x = 0; x < glyphImage->width; x++) {
	    XPutPixel(glyphImage, x, y, colorContextPtr->bg->pixel);
	}
	y++;
    }

    *dxPtr = glyphPtr->hShrinkOffset = refX;
    *dyPtr = glyphPtr->vShrinkOffset = glyphPtr->vertOffset / shrink;
    /* fprintf(stderr, " dx=%d dy=%d OK\n", *dxPtr, *dyPtr); */

    glyphPtr->shrink = shrink;
    glyphPtr->fgUid = (ClientData)colorContextPtr->fgUid;
    /* glyphPtr->bgUid = colorContextPtr->bgUid; */

    return glyphImage;
}

static void
RenderGlyph (clientData, dviInterp, x, y, fontPtr, character, tfmWidthPtr,
	     pixelWidthPtr)
    ClientData clientData;
    Dvi_Interp *dviInterp;	/* current DVI interpreter */
    S32 x, y;			/* current position in device pixels */
    Dvi_Font *fontPtr;		/* current font */
    S32 character;		/* character position to be typeset */
    S32 *tfmWidthPtr;		/* for returning character's TFM width */
    S32 *pixelWidthPtr;		/* for returning character's pixel width */
{
    RenderData *renderDataPtr = (RenderData *)clientData;
    Dvi_Glyph *glyphPtr;
    S32 dx, dy;

    XImage *glyphImage;

    /*
     * Find a glyph for the character in question. If there is no
     * glyph, then go away. This is lame.
     */

    /* fprintf(stderr, "chr %c ", (U8)character); */
    glyphPtr = Dvi_FontGetGlyph(fontPtr, character, tfmWidthPtr,pixelWidthPtr);
    if (glyphPtr == (Dvi_Glyph *)0) {
#if 0
	fprintf(stderr, "NG\n");
#endif
	return;
    }

    /*fprintf(stderr, "%d ", *pixelWidthPtr);*/

    if (renderDataPtr->masterPtr->currLayer
	> renderDataPtr->masterPtr->layer) {
	return;
    }

    /*
     * Adjust the target coordinates for the DVI origin ((1in,1in) by
     * default), the shrink factor and the offset of the reference point
     * within the glyph, taking into account the glyph reduction.
     */

#define SHRINK (renderDataPtr->masterPtr->shrink)

    x += renderDataPtr->masterPtr->originX;
    y += renderDataPtr->masterPtr->originY;

    if ((y + glyphPtr->height - glyphPtr->vertOffset)/SHRINK < renderDataPtr->minY
	|| (x + glyphPtr->width - glyphPtr->horizOffset)/SHRINK < renderDataPtr->minX
	|| (y - glyphPtr->vertOffset)/SHRINK > renderDataPtr->maxY
	|| (x - glyphPtr->horizOffset)/SHRINK > renderDataPtr->maxX) {
	/* fprintf(stderr, "off ((%d+%d)/%d=%d<%d || %d/%d=%d>%d\n", x,glyphPtr->width,SHRINK,(x+glyphPtr->width)/SHRINK,renderDataPtr->minX,x,SHRINK,x/SHRINK,renderDataPtr->maxX); */
	return;
    }
    /* fprintf(stderr, "%c", (U8)character); */

    /*
     * Find or make an X image for the glyph in question, at the
     * appropriate reduction.
     */

    if (SHRINK == 1) {
	glyphImage = GlyphToImageOriginal(renderDataPtr->instancePtr,
					  glyphPtr, &dx, &dy);
	x -= dx;
	y -= dy;
    } else {
	glyphImage = GlyphToImageShrink(renderDataPtr->instancePtr,
					glyphPtr, &dx, &dy, SHRINK);
	x = x / SHRINK - dx;
	y = y / SHRINK - dy;
    }

    /*
     * Render the glyph (finally).
     */

    XPutImage(renderDataPtr->display, renderDataPtr->drawable,
	      renderDataPtr->instancePtr->colorStack[renderDataPtr->instancePtr->colorStackIdx]->gc,
	      glyphImage, 0, 0,
	      x - renderDataPtr->minX + renderDataPtr->drawableX,
	      y - renderDataPtr->minY + renderDataPtr->drawableY,
	      (unsigned int)glyphImage->width,
	      (unsigned int)glyphImage->height);
}

static void
RenderRule (clientData, dviInterp, x, y, width, height)
    ClientData clientData;
    Dvi_Interp *dviInterp;
    S32 x, y;
    S32 width, height;
{
    RenderData *renderDataPtr = (RenderData *)clientData;

    if (renderDataPtr->masterPtr->currLayer
	> renderDataPtr->masterPtr->layer) {
	return;
    }

    x += renderDataPtr->masterPtr->originX;
    y += renderDataPtr->masterPtr->originY - height;

    if ((y + height)/SHRINK < renderDataPtr->minY
	|| (x + width)/SHRINK < renderDataPtr->minX
	|| y/SHRINK > renderDataPtr->maxY
	|| x/SHRINK > renderDataPtr->maxX) {
	return;
    }
    x /= SHRINK;
    y /= SHRINK;

    if (width != 0) {
	width /= SHRINK;
	if (width == 0) {
	    width = 1;
	}
    }

    if (height != 0) {
	height /= SHRINK;
	if (height == 0) {
	    height = 1;
	}
    }

    XFillRectangle(renderDataPtr->display, renderDataPtr->drawable,
		   renderDataPtr->instancePtr->colorStack[renderDataPtr->instancePtr->colorStackIdx]->gc,
		   x - renderDataPtr->minX + renderDataPtr->drawableX,
		   y - renderDataPtr->minY + renderDataPtr->drawableY,
		   (unsigned int)width, (unsigned int)height);
}

static int
RenderFontDef (clientData, dviInterp, fontListPtr, fontNum,
	       check, fontScale, designSize, nameLen, name, mode)
    ClientData clientData;
    Dvi_Interp *dviInterp;
    Dvi_FontList **fontListPtr;
    S32 fontNum;
    U32 check;
    U32 fontScale;
    U32 designSize;
    size_t nameLen;
    char *name;
    Dvi_FontDefMode mode;
{
    /* fprintf(stderr, "font %ld: %*.*s mode=%d\n",
       (long int)fontNum, (int)nameLen, (int)nameLen, name, mode); */
    if (mode != DVI_FMODE_DVI) {
	Dvi_FontAdd(dviInterp, fontListPtr, fontNum,
		    check, fontScale, designSize, nameLen, name);
    }
    return TCL_OK;
}

static int
RenderSpecial(clientData, dviInterp, code, x, y, length, string)
    ClientData clientData;
    struct Dvi_Interp *dviInterp;
    S32 x, y;
    U32 length;
    const char *string;
{
    RenderData *renderDataPtr = (RenderData *)clientData;
    DviMaster *masterPtr = renderDataPtr->masterPtr;

    if (strncmp(string, "color push ", 11) == 0) {
	PushFgColor(renderDataPtr->instancePtr, string+11, length-11);
    } else if (strncmp(string, "color pop", 9) == 0) {
	PopFgColor(renderDataPtr->instancePtr);
    } else if (strncmp(string, "layer push", 10) == 0) {
	masterPtr->currLayer++;
	if (masterPtr->currLayer > masterPtr->maxLayer) {
	    masterPtr->maxLayer = masterPtr->currLayer;
	}
    } else if (strncmp(string, "layer pop", 9) == 0) {
	masterPtr->currLayer--;
    } else if (strncmp(string, "layer reset", 11) == 0) {
	masterPtr->currLayer = 0;
    } else if (masterPtr->specialCommand) {
	char *p, *q;
	char *cmd;
	size_t cmdLength = 0;
	char *imgCmdName;

	/*
	 * Calculate the length of the command string in order to
	 * allocate a buffer.
	 */

	for (p = masterPtr->specialCommand; *p; p++) {
	    if (*p == '%') {
		switch (*++p) {
		case 'x': case 'y': case 'c':
		    cmdLength += 11; break; /* max length of 32-bit number */
		case 's':
		    cmdLength += length + 2; break; /* {...} */
		case 'i':
		    imgCmdName = Tcl_GetCommandName(
			masterPtr->interp, masterPtr->imageCmd);
		    cmdLength += strlen(imgCmdName); break;
		case '%':
		    cmdLength++; break;
		default:
		    fprintf(stderr,
			"invalid substitution \"%%%c\" in specialCommand\n",
			    *p);
		    return TCL_ERROR;
		}
	    } else {
		cmdLength++;
	    }
	}

	if ((cmd = ckalloc(cmdLength + 1)) == (char *)0) {
	    fprintf(stderr, "no memory for special command\n");
	    return TCL_ERROR;
	}

	x = (x + masterPtr->originX) / SHRINK;
	y = (y + masterPtr->originY) / SHRINK;

	/*
	 * Generate the actual command string and hand it off to Tcl
	 * for execution.
	 */

	q = cmd;
	for (p = masterPtr->specialCommand; *p; p++) {
	    if (*p == '%') {
		switch (*++p) {
		case 'x': q += sprintf(q, "%ld", (long)x); break;
		case 'y': q += sprintf(q, "%ld", (long)y); break;
		case 'c': q += sprintf(q, "%p", code); break;
		case 's':
		    q += sprintf(q, "{%*.*s}",
				 (int)length, (int)length, string);
		    break;
		case 'i': q += sprintf(q, "%s", imgCmdName); break;
		case '%': *q++ = '%'; break;
		default:
		    fprintf(stderr, "This can't happen (\"%%%c\")\n", *p);
		    *q++ = *p;
		    break;
		}
	    } else {
		*q++ = *p;
	    }
	}
	*q = '\0';
	/* fprintf(stderr, "RenderSpecial: cmd=`%s'\n", cmd); */
	Tcl_GlobalEval(masterPtr->interp, cmd);
    }
    return TCL_OK;
}

static void
DviImgDisplay(clientData, display, drawable, imageX, imageY, width,
	      height, drawableX, drawableY)
    ClientData clientData;      /* Pointer to DviInstance structure for
                                 * instance to be displayed. */
    Display *display;           /* Display on which to draw image. */
    Drawable drawable;          /* Pixmap or window in which to draw image. */
    int imageX, imageY;         /* Upper-left corner of region within image
                                 * to draw. */
    int width, height;          /* Dimensions of region within image to draw */
    int drawableX, drawableY;   /* Coordinates within drawable that
                                 * correspond to imageX and imageY. */
{
    DviInstance *instancePtr = (DviInstance *)clientData;
    RenderData renderData;
    Dvi_File *dviFile;
    U8 *dviCodePtr;
#if MICKEY_MOUSE
    char buffer[1024];
    char *p;
#endif /* MICKEY_MOUSE */

#if BLAFASEL
    if (instancePtr->gc == None) {
	return;
    }
#endif

    if (mirrorByte[1] != 0x80) {
	int i;
	static U8 mirrorNybble[16] = {
	    0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
	};
	for (i = 0; i < 256; i++) {
	    mirrorByte[i] = (mirrorNybble[i%16] << 4) | mirrorNybble[i/16];
	}
    }

    /* fprintf(stderr, "RENDER>>> (%d, %d) [%d, %d] @ (%d, %d) M: w %d h %d\n",
	    imageX, imageY, width, height, drawableX, drawableY,
	    instancePtr->masterPtr->width, instancePtr->masterPtr->height); */

/*    fprintf(stderr, "Render: instancePtr->bg->pixel=%08lx\n", instancePtr->colorStack[instancePtr->colorStackIdx]->bg->pixel); */
    XFillRectangle(display, drawable,
		   instancePtr->colorStack[instancePtr->colorStackIdx]->bgGc,
		   drawableX - imageX,
		   drawableY - imageY,
		   instancePtr->masterPtr->width-1,
		   instancePtr->masterPtr->height-1);
    XDrawRectangle(display, drawable,
		   instancePtr->colorStack[instancePtr->colorStackIdx]->gc,
		   drawableX - imageX,
		   drawableY - imageY,
		   instancePtr->masterPtr->width-1,
		   instancePtr->masterPtr->height-1);

    if (!instancePtr->masterPtr->render) {
	XDrawLine(display, drawable,
		  instancePtr->colorStack[instancePtr->colorStackIdx]->gc,
		  drawableX - imageX,
		  drawableY - imageY,
		  drawableX - imageX + instancePtr->masterPtr->width,
		  drawableY - imageY + instancePtr->masterPtr->height);
	XDrawLine(display, drawable,
		  instancePtr->colorStack[instancePtr->colorStackIdx]->gc,
		  drawableX - imageX + instancePtr->masterPtr->width,
		  drawableY - imageY,
		  drawableX - imageX,
		  drawableY - imageY + instancePtr->masterPtr->height);
	return;
    }

#if MICKEY_MOUSE
    XDrawLine(display, drawable, instancePtr->gc,
	      drawableX - imageX,
	      drawableY - imageY,
	      drawableX - imageX + instancePtr->masterPtr->width,
	      drawableY - imageY + instancePtr->masterPtr->height);
    XDrawLine(display, drawable, instancePtr->gc,
	      drawableX - imageX + instancePtr->masterPtr->width,
	      drawableY - imageY,
	      drawableX - imageX,
	      drawableY - imageY + instancePtr->masterPtr->height);
    sprintf(buffer, "%s %d", instancePtr->masterPtr->fileString,
	    instancePtr->masterPtr->pageNum);
    XDrawString(display, drawable, instancePtr->gc,
		drawableX, drawableY + (height/2),
		buffer, strlen(buffer));
    XDrawLine(display, drawable, instancePtr->gc,
	      drawableX - imageX + instancePtr->masterPtr->originX - 5,
	      drawableY - imageY + instancePtr->masterPtr->originY + 5,
	      drawableX - imageX + instancePtr->masterPtr->originX + 5,
	      drawableY - imageY + instancePtr->masterPtr->originY - 5);
    XDrawLine(display, drawable, instancePtr->gc,
	      drawableX - imageX + instancePtr->masterPtr->originX + 5,
	      drawableY - imageY + instancePtr->masterPtr->originY + 5,
	      drawableX - imageX + instancePtr->masterPtr->originX - 5,
	      drawableY - imageY + instancePtr->masterPtr->originY - 5);
#else /* !MICKEY_MOUSE */
    renderData.masterPtr = instancePtr->masterPtr;
    renderData.instancePtr = instancePtr;
    renderData.display = display;
    renderData.drawable = drawable;
    renderData.minX = imageX;
    renderData.maxX = imageX + width;
    renderData.minY = imageY;
    renderData.maxY = imageY + height;
    renderData.drawableX = drawableX;
    renderData.drawableY = drawableY;

    instancePtr->masterPtr->dviInterp->procData = (ClientData)&renderData;
    instancePtr->masterPtr->dviInterp->glyphProc = RenderGlyph;
    instancePtr->masterPtr->dviInterp->ruleProc = RenderRule;
    instancePtr->masterPtr->dviInterp->fontDefProc = RenderFontDef;
    instancePtr->masterPtr->dviInterp->specialProc = RenderSpecial;

    dviFile = Dvi_GetFileByCookie(instancePtr->masterPtr->interp,
				  instancePtr->masterPtr->fileString, 0);
    if (dviFile == (Dvi_File *)0) {
	return;
    }

    /*
     * Check whether DVI file was changed without telling us. If so,
     * reload the file, reset the interpreter and start over with
     * (possibly) new or different fonts.
     *
     * We should probably call Tk_BackgroundError() if anything went
     * wrong with the fonts.
     */
     
    if (Dvi_FileChanged(dviFile)) {
	Dvi_FileReload(dviFile, (Dvi_ErrorProc *)0, (ClientData)0); /* Yuck. */
	Dvi_ResetInterp(instancePtr->masterPtr->dviInterp, 1);
	instancePtr->masterPtr->loadFonts = 1;
    }

    /*
     * Load fonts if we haven't done so already. We used to have a call to
     * Dvi_FontsFromPostamble() in the DviImgConfigureMaster() function
     * above, but this could lead to the function being called twice.
     * We need to be able to re-read the fonts here, in case the DVI file
     * has changed, so now we just set a flag in DviImgConfigureMaster()
     * if the fonts need to be read. The same flag is set if the DVI
     * file has just been reloaded above.
     */

    if (instancePtr->masterPtr->loadFonts) {
	if (Dvi_FontsFromPostamble(instancePtr->masterPtr->dviInterp, dviFile)
	    != TCL_OK) {
	    instancePtr->masterPtr->render = 0;
	    return;
	}
	instancePtr->masterPtr->loadFonts = 0;
    }

    /*
     * Find the start of the page for this image in the DVI code. It might
     * be possible, if the DVI file has just been re-read, that the desired
     * page no longer exists in the file (although the page number was
     * checked when it was first specified in a `<image> page n' command.
     * Thus bail out if the code couldn't be found.
     */

    dviCodePtr = Dvi_CodeFindCodeForPage(dviFile->infoPtr->codePtr,
					 instancePtr->masterPtr->pageNum);
    if (dviCodePtr == (U8 *)0) {
	return;
    }

    /*
     * Now render the page in the window. Actual DVI code starts
     * 45 bytes beyond the start of the page.
     */

    dviCodePtr += 45;
    instancePtr->masterPtr->currLayer = 0;
    Dvi_ResetInterp(instancePtr->masterPtr->dviInterp, 0);
    Dvi_Interpret(instancePtr->masterPtr->dviInterp, dviCodePtr);
#endif /* MICKEY_MOUSE */
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgFree --
 *
 *      This procedure is called when a widget ceases to use a
 *      particular instance of an image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Internal data structures get cleaned up.
 *
 *----------------------------------------------------------------------
 */

static void
DviImgFree(clientData, display)
    ClientData clientData;      /* Pointer to DviInstance structure for
                                 * for instance to be displayed. */
    Display *display;           /* Display containing window that used image */
{
    DviInstance *instancePtr = (DviInstance *)clientData;
    DviInstance *prevPtr;

    instancePtr->refCount--;
    if (instancePtr->refCount > 0) {
        return;
    }

    /*
     * There are no more uses of the image within this widget.  Free
     * the instance structure.
     */

    FreeColorContextCache(instancePtr);

    if (instancePtr->masterPtr->instancePtr == instancePtr) {
        instancePtr->masterPtr->instancePtr = instancePtr->nextPtr;
    } else {
        for (prevPtr = instancePtr->masterPtr->instancePtr;
                prevPtr->nextPtr != instancePtr; prevPtr = prevPtr->nextPtr) {
            /* Empty loop body */
        }
        prevPtr->nextPtr = instancePtr->nextPtr;
    }
    ckfree((char *) instancePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgDelete --
 *
 *      This procedure is called by the image code to delete the
 *      master structure for an image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Resources associated with the image get freed.
 *
 *----------------------------------------------------------------------
 */

static void
DviImgDelete(masterData)
    ClientData masterData;      /* Pointer to DviMaster structure for
                                 * image.  Must not have any more instances. */
{
    DviMaster *masterPtr = (DviMaster *)masterData;

    if (masterPtr->instancePtr != 0) {
        panic("tried to delete DVI image when instances still exist");
    }
    masterPtr->tkMaster = (Tk_ImageMaster)0;
    if (masterPtr->imageCmd != 0) {
        Tcl_DeleteCommand(masterPtr->interp,
                Tcl_GetCommandName(masterPtr->interp, masterPtr->imageCmd));
    }

    /*
     * DVI-specific stuff goes here.
     */
    if (masterPtr->dviInterp != (Dvi_Interp *)0) {
	Dvi_DeleteInterp(masterPtr->dviInterp);
    }

    Tk_FreeOptions(configSpecs, (char *)masterPtr, (Display *)0, 0);
    ckfree((char *)masterPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DviImgCmdDeletedProc --
 *
 *      This procedure is invoked when the image command for an image
 *      is deleted.  It deletes the image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The image is deleted.
 *
 *----------------------------------------------------------------------
 */

static void
DviImgCmdDeletedProc(clientData)
    ClientData clientData;      /* Pointer to DviMaster structure for
                                 * image. */
{
    DviMaster *masterPtr = (DviMaster *)clientData;

    masterPtr->imageCmd = 0;
    if (masterPtr->tkMaster != (Tk_ImageMaster)0) {
        Tk_DeleteImage(masterPtr->interp, Tk_NameOfImage(masterPtr->tkMaster));
    }
}

#endif /* ENABLE_TK */
