/*
 * dviInterp.c --
 *
 *      This file implements a DVI interpreter.
 *
 * Copyright © 1999 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>
 * See file COPYING for conditions on use and distribution.
 */

#include <math.h>
#include <string.h>

#include "dviInt.h"
#include "dviOps.h"

#ifndef lint
static char rcsid[] VAR_UNUSED = "$Id: dviInterp.c,v 1.5 2000/05/18 12:50:29 lingnau Exp $";
#endif /* lint */

#define STD_MAXDRIFT 2		/* standard maximum deviation between true */
                                /* and rounded pixel positions */

static U8 *GetFirstParameter _ANSI_ARGS_((int c, U8 *codePtr, S32 *valuePtr,
                                          Dvi_StackFrame *contextPtr));

static void stdGlyph _ANSI_ARGS_((ClientData, Dvi_Interp *, S32, S32,
                                  Dvi_Font *, S32, S32 *, S32 *));
static void stdRule _ANSI_ARGS_((ClientData, Dvi_Interp *, S32, S32,
                                 S32, S32));
static int stdFontDef _ANSI_ARGS_((ClientData, Dvi_Interp *, Dvi_FontList **,
				   S32, U32, U32, U32, size_t, char *,
				   Dvi_FontDefMode));
static int stdSpecial _ANSI_ARGS_((ClientData, Dvi_Interp *, U8 *, S32, S32,
                                   U32, const char *));

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CreateInterp --
 *
 *      Allocates and initializes a Dvi_Interp structure.
 *
 * Results:
 *      The function returns a pointer to the Dvi_Interp structure or
 *      the null pointer if an error occurred.
 *
 * Side effects:
 *      A Dvi_Interp structure is allocated and initialized.
 *
 * ------------------------------------------------------------------------
 */

Dvi_Interp *
Dvi_CreateInterp(interp, xRes, yRes, stackSize, numerator, denominator,
		 magnification)
    Tcl_Interp *interp;         /* Tcl interpreter for error reporting etc. */
    unsigned int xRes;          /* Horizontal resolution of rendering device */
    unsigned int yRes;          /* Vertical resolution of rendering device */
    unsigned int stackSize;     /* Desired size for DVI evaluation stack */
    U32 numerator, denominator;	/* Parameters from DVI file */
    U32 magnification;          /* DVI document scaling factor */
{
    Dvi_Interp *dviInterp;      /* DVI interpreter being initialized */

    /*
     * Elementary sanity check on arguments.
     */

    if (xRes == 0 || yRes == 0 || stackSize == 0) {
        return (Dvi_Interp *)0;
    }

    /*
     * Allocate memory for the interpreter and stacks.
     */

    dviInterp = (Dvi_Interp *)ckalloc(sizeof(Dvi_Interp));
    if (dviInterp == 0) {
        return (Dvi_Interp *)0;
    }

    /*
     * Allocate the stack with a reserve for virtual fonts.
     */

    dviInterp->stack = (Dvi_StackFrame *)ckalloc((stackSize + 10)
						 * sizeof(Dvi_StackFrame));
    if (dviInterp->stack == 0) {
        ckfree((char *)dviInterp);
        return (Dvi_Interp *)0;
    }
    dviInterp->stackSize = stackSize + 10;

    /*
     * Initialize the various fields.
     */

    dviInterp->interp = interp;
    dviInterp->dviCode = (Dvi_Code *)0;
    Dvi_SetResolution(dviInterp, xRes, yRes, numerator, denominator,
                      magnification);
    dviInterp->maxDrift = STD_MAXDRIFT;
    dviInterp->stackPtr = 0;
    dviInterp->fonts = (Dvi_FontList *)0;
    
    dviInterp->procData = (ClientData)0;
    dviInterp->glyphProc = stdGlyph;
    dviInterp->ruleProc = stdRule;
    dviInterp->fontDefProc = stdFontDef;
    dviInterp->specialProc = stdSpecial;
    
    return dviInterp;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CreateInterpForCode --
 *
 *      Allocates and initializes a Dvi_Interp structure matching a
 *      previously loaded piece of DVI code.
 *
 * Results:
 *      The function returns a pointer to the Dvi_Interp structure or
 *      the null pointer if an error occurred.
 *
 * Side effects:
 *      A Dvi_Interp structure is allocated and initialized.
 *
 * ------------------------------------------------------------------------
 */

Dvi_Interp *
Dvi_CreateInterpForCode (interp, xRes, yRes, dviCode)
    Tcl_Interp *interp;		/* Tcl interpreter for error reporting */
    unsigned int xRes;		/* Horizontal resolution of interpreter */
    unsigned int yRes;		/* Vertical resolution of interpreter */
    Dvi_Code *dviCode;		/* DVI file to be used */
{
    Dvi_Interp *dviInterp;
    
    dviInterp = Dvi_CreateInterp(interp, xRes, yRes,
				 dviCode->stackSize,
                                 dviCode->num, dviCode->den,
				 dviCode->mag);
    if (dviInterp != (Dvi_Interp *)0) {
        dviInterp->dviCode = dviCode;
    }
    return dviInterp;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_SetResolution --
 *
 *      Sets the resolution parameters of a DVI interpreter and calculates
 *      the appropriate internal conversion factors.
 *
 * Results:
 *      If any of the arguments are zero, TCL_ERROR is
 *      returned. Otherwise the result is TCL_OK.
 *
 * Side effects:
 *      Changes conversion factors in *dvi_interp.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_SetResolution(dviInterp, xRes, yRes, numerator, denominator, magnification)
    Dvi_Interp *dviInterp;      /* DVI interpreter to be modified */
    unsigned int xRes, yRes;	/* Device resolution in pixels per inch */
    U32 numerator, denominator, magnification; /* DVI dimension parameters */
{
    if (xRes == 0 || yRes == 0) {
        return TCL_ERROR;
    }

    dviInterp->tfmConv = (25400000.0/numerator)*(denominator/473628672)/16.0;

    dviInterp->xResolution = xRes;
    dviInterp->trueXConv = (numerator/254000.0) * ((double)xRes/denominator);
    dviInterp->xConv = dviInterp->trueXConv * (magnification/1000.0);

    dviInterp->yResolution = yRes;
    dviInterp->trueYConv = (numerator/254000.0) * ((double)yRes/denominator);
    dviInterp->yConv = dviInterp->trueYConv * (magnification/1000.0);

    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_DeleteInterp --
 *
 *      Deletes a DVI interpreter.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The DVI interpreter pointed to by `dviInterp' is removed.
 *
 * ------------------------------------------------------------------------
 */

void
Dvi_DeleteInterp (dviInterp)
    Dvi_Interp *dviInterp;
{
    Dvi_ResetInterp(dviInterp, 1);
    ckfree((char *)dviInterp->stack);
    ckfree((char *)dviInterp);
    Dvi_FontPurge();
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_ResetInterp --
 *
 *      Resets a DVI interpreter's internal state in preparation for
 *      the translation of DVI code.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      This zeroes all the DVI internal registers of the DVI interpreter
 *      and clears the DVI stack. If clearFonts is non-zero, it frees
 *      all the fonts used in this interpreter.
 *
 * ------------------------------------------------------------------------
 */

void
Dvi_ResetInterp (dviInterp, clearFonts)
    Dvi_Interp *dviInterp;
    int clearFonts;		/* free fonts if non-zero */
{
    dviInterp->stackPtr = 0;
    dviInterp->stack[dviInterp->stackPtr].h = 0;
    dviInterp->stack[dviInterp->stackPtr].v = 0;
    dviInterp->stack[dviInterp->stackPtr].w = 0;
    dviInterp->stack[dviInterp->stackPtr].x = 0;
    dviInterp->stack[dviInterp->stackPtr].y = 0;
    dviInterp->stack[dviInterp->stackPtr].hh = 0;
    dviInterp->stack[dviInterp->stackPtr].vv = 0;

    if (clearFonts) {
	Dvi_FontList *listPtr, *nextPtr; /* used for freeing fonts */

	/*
	 * Walk the list of fonts, freeing fonts as you go
	 */

	for (listPtr = dviInterp->fonts;
	     listPtr != (Dvi_FontList *)0;
	     listPtr = nextPtr) {
	    nextPtr = listPtr->nextPtr;
	    Dvi_FontFree(listPtr->fontPtr);
	    ckfree((char *)listPtr);
	}
	dviInterp->fonts = (Dvi_FontList *)0;
    }
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontsFromPostamble --
 *
 *      Process list of font definitions given in a DVI file's postamble.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      Loads the appropriate fonts if necessary.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_FontsFromPostamble(dviInterp, dviFile)
    Dvi_Interp *dviInterp;	/* DVI interpreter for resolution etc. */
    Dvi_File *dviFile;		/* File whose postamble is being looked at */
{
    U8 *code;			/* Pointer to font definition */
    S32 fontNum;		/* Font number being defined */

    if (dviFile->infoPtr->postamble == (U8 *)0) {
        return TCL_ERROR;
    }

    code = dviFile->infoPtr->postamble + 29;
    while (*code >= D_FNTDEF1 && *code <= D_FNTDEF4) {
        code = GetFirstParameter(*code, code+1, &fontNum, (Dvi_StackFrame *)0);
        code = Dvi_ProcessFontDef(dviInterp, &dviInterp->fonts, fontNum,
				  code, DVI_FMODE_POST);
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_ProcessFontDef --
 *
 *      Handle a single font definition (in or outside postamble). This
 *      code assumes that the first font definition parameter (the font
 *      number) has already been read and is being passed as a parameter
 *      to this function.
 *
 * Results:
 *      Returns a pointer to the DVI code byte immediately beyond the
 *      font definition.
 *
 * Side effects:
 *      May invoke the fontdefProc in order to actually load a font.
 *
 * ------------------------------------------------------------------------
 */

U8 *
Dvi_ProcessFontDef (dviInterp, fontListPtr, fontNum, fontDefPtr, mode)
    Dvi_Interp *dviInterp;
    Dvi_FontList **fontListPtr;
    S32 fontNum;
    U8 *fontDefPtr;
    Dvi_FontDefMode mode;
{
    U32 check;                  /* Font checksum */
    U32 fontScale;              /* Font scaling factor */
    U32 designSize;             /* Font 'design size' */
    unsigned int nameLen;       /* Length of font name */

    /*
     * Obtain font parameters from DVI file.
     */

    check = DviGetU32(fontDefPtr);
    fontScale = DviGetU32(fontDefPtr + 4);
    designSize = DviGetU32(fontDefPtr + 8);
    nameLen = DviGetU8(fontDefPtr+12) + DviGetU8(fontDefPtr+13);

    if (mode != DVI_FMODE_DVI) {
	(dviInterp->fontDefProc)(dviInterp->procData,
				 dviInterp, fontListPtr, fontNum, check,
				 fontScale, designSize, (size_t)nameLen,
				 (char *)(fontDefPtr + 14), mode);
    }
    return fontDefPtr + nameLen + 14;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_Interpret --
 *
 *      Render a piece of DVI code in the context of *dviInterp.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      The rendering procedures defined in the DVI interpreter are
 *      called at appropriate moments during the interpretation.
 *
 * ------------------------------------------------------------------------
 */

#define CONTEXT  (&dviInterp->stack[dviInterp->stackPtr])
#define H (CONTEXT->h)
#define V (CONTEXT->v)
#define W (CONTEXT->w)
#define X (CONTEXT->x)
#define Y (CONTEXT->y)
#define Z (CONTEXT->z)
#define HH (CONTEXT->hh)
#define VV (CONTEXT->vv)

int
Dvi_Interpret(dviInterp, code)
    Dvi_Interp *dviInterp;
    U8 *code;
{
    if (code == (U8 *)0) {
        return TCL_ERROR;
    }

    return Dvi_DoInterpret(dviInterp, code, (Dvi_Font *)0, dviInterp->fonts);
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_DoInterpret --
 *
 *      Render a piece of DVI code in the context of *dviInterp. This
 *      procedure does the actual work; it calls itself recursively
 *      to deal with characters from virtual fonts.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      The rendering procedures defined in the DVI interpreter are
 *      called at appropriate moments during the interpretation.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_DoInterpret(dviInterp, code, f, fontListPtr)
    Dvi_Interp *dviInterp;
    U8 *code;
    Dvi_Font *f;
    Dvi_FontList *fontListPtr;
{
    S32 fontSpaceLeft, fontSpaceRight, fontSpaceVert;
    U32 maxDrift;               /* cache value from dviInterp */
    U8 *codeStart = code;

    maxDrift = dviInterp->maxDrift;
    if (f == (Dvi_Font *)0) {
	fontSpaceLeft = fontSpaceRight = fontSpaceVert = 0;
    } else {
	fontSpaceRight = f->fontScale / 6; /* see dvitype.web §63 */
	fontSpaceLeft = -4 * fontSpaceRight;
	fontSpaceVert = 5 * fontSpaceRight;
    }
    while (*code != D_EOP) {
        U8 o;                   /* DVI command currently being executed */
        S32 p, q;               /* Parameters for current DVI command */

	o = *code++;
        code = GetFirstParameter(o, code, &p, CONTEXT);
        switch (o) {
            case D_SET1: case D_SET2: case D_SET3: case D_SET4:
            case D_PUT1: case D_PUT2: case D_PUT3: case D_PUT4:
          finishSet:
	    /*
	     * Typeset character p from font f, using the device-specific
	     * rendering procedure. If we're processing a D_PUTx command,
	     * don't move the reference point afterwards.
	     */

            if (f == (Dvi_Font *)0) {
                break;
            }
            
            {
                S32 pixelX;	/* Width of character in device pixels */

		/*
		 * If the current font is a virtual font we do not call
		 * the current glyphProc but arrange for the virtual
		 * character's DVI snippet to be executed.
		 *
		 * The case where a character is simply replaced with
		 * a single character from the default font could be
		 * improved to remove the PUSH and POP operations.
		 */

		if (f->type == dvi_font_vf) {
		    U8 *codePtr;
		    Dvi_Font *firstFont;
		    Dvi_FontList *fontListPtr;

		    if (dviInterp->stackPtr == dviInterp->stackSize) {
			/*
			 * Dynamically enlarge the DVI stack
			 */
		    }
		    CONTEXT[1] = CONTEXT[0];
		    dviInterp->stackPtr++;
		    
		    if (Dvi_GetVFInfo(f, p, &q, &pixelX, &firstFont, &codePtr,
				      &fontListPtr) == TCL_OK) {
			Dvi_DoInterpret(dviInterp, codePtr, firstFont,
					fontListPtr);
		    }
    		    dviInterp->stackPtr--;
		} else {
		    (* dviInterp->glyphProc)(dviInterp->procData, dviInterp,
					     HH, VV, f, p, &q, &pixelX);
		}

                if (o >= D_PUT1) {
                    break;
                }
                HH += pixelX;
            }
            goto moveRight;

            /*
             * Typeset a rule using the device-specific rendering procedure.
             * Fetch the rule width into q first. If the rule width or
             * height are negative, nothing gets typeset. There is a special
             * rounding procedure for rules to make sure that they abut
             * correctly. As usual, the D_PUTRULE command does not move
             * the reference point.
             */

#define RulePixX(p) ((S32)(ceil(dviInterp->xConv * (double)(p))))
#define RulePixY(p) ((S32)(ceil(dviInterp->yConv * (double)(p))))

            case D_SETRULE: case D_PUTRULE:
                q = DviGetS32(code); code += 4;
                if (p >= 0 && q >= 0) {
                    (* dviInterp->ruleProc)(dviInterp->procData, dviInterp,
                                            HH, VV, RulePixX(q), RulePixY(p));
                }
                if (o == D_PUTRULE) {
                    break;
                }
                HH += RulePixX(q);
                goto moveRight;
                
                /*
                 * The various horizontal movement commands differ in the
                 * size of their arguments and in whether one of the DVI
                 * machine registers gets updated.
                 */

            case D_W0: case D_W1: case D_W2: case D_W3: case D_W4:
                W = p; goto outSp;
            case D_X0: case D_X1: case D_X2: case D_X3: case D_X4:
                X = p; goto outSp;
            case D_RIGHT1: case D_RIGHT2: case D_RIGHT3: case D_RIGHT4:

                /*
                 * Here's where the fun starts. There is an intricate
                 * procedure to make sure the output looks nice --
                 * since the horizontal position in DVI units (scaled
                 * points) is likely to differ from the horizontal
                 * position in device pixels, we need to update the
                 * device position carefully. If we just rounded the
                 * DVI position to calculate the device position, the
                 * spaces between characters within a word might
                 * differ, creating a jagged look. What we do is add
                 * the distance measured in pixels to the device
                 * position without rounding if it is not too big
                 * (say, between the characters in a word).  If the
                 * distance is bigger than a certain threshold
                 * (fontSpace, which depends on the font currently in
                 * use) we assume that this is a space between two
                 * words and take the opportunity to resynchronize the
                 * DVI and device positions. Note that the threshold
                 * is different for movements to the left than for
                 * movements to the right; this is because larger
                 * movements to the left can occur within words if
                 * characters are accented. Also note that the device
                 * position is allowed to differ from the DVI position
                 * only by a maximum of maxDrift pixels. This is more
                 * extensively documented in dvitype.web. Note that
                 * this algorithm is taken from Knuth's original
                 * sources; the DVI driver standard defines the
                 * fontSpace values differently.  When in doubt, I'd
                 * rather stick with Knuth, thank you.
                 */

#define PixRoundX(p) ((S32)floor(dviInterp->xConv * (double)(p) + 0.5))
#define PixRoundY(p) ((S32)floor(dviInterp->yConv * (double)(p) + 0.5))

          outSp:
            HH = (p >= fontSpaceRight || p <= fontSpaceLeft)
                ? PixRoundX(H + p) : HH + PixRoundX(p);
            q = p;
          moveRight:
            {
                S32 hhh = PixRoundX(H + q);
                if ((U32)ABS(hhh - HH) > dviInterp->maxDrift) {
                    HH = (hhh > HH)
                        ? hhh - dviInterp->maxDrift
                        : hhh + dviInterp->maxDrift;
                }
            }
            H += q;
	    break;
	    
	    /*
	     * Vertical movement is handled pretty much like horizontal
	     * movement.
	     */
        
            case D_Y0: case D_Y1: case D_Y2: case D_Y3: case D_Y4:
                Y = p; goto moveDown;
            case D_Z0: case D_Z1: case D_Z2: case D_Z3: case D_Z4:
                Z = p; goto moveDown;
            case D_DOWN1: case D_DOWN2: case D_DOWN3: case D_DOWN4:
          moveDown:
            VV = (ABS(p) >= fontSpaceVert)
                ? PixRoundY(V + p) : VV + PixRoundY(p);
            {
                S32 vvv = PixRoundY(V + p);
                if ((U32)ABS(vvv - VV) >= maxDrift) {
                    VV = (vvv > VV) ? vvv - maxDrift : vvv + maxDrift;
                }
            }
            V += p;
            break;
            
            /*
             * Here we handle font selection. The threshold parameters for
             * rounding movement are updated according to the new font.
             */
            
            case D_FNT1: case D_FNT2: case D_FNT3: case D_FNT4:
          changeFont:
            {
                Dvi_FontList *listPtr;
                
                for (listPtr = dviInterp->fonts;
                     listPtr != (Dvi_FontList *)0 && p != listPtr->fontNum;
                     listPtr = listPtr->nextPtr)
		    ;

                if (listPtr != (Dvi_FontList *)0) {
                    f = listPtr->fontPtr;
                    if (f == (Dvi_Font *)0) {
                        fprintf(stderr, "No font for number %ld\n",
                                (long)p);
                    }
                } else {
                    fprintf(stderr, "%d: (%d) Font number %ld not found\n",
                            code - codeStart, o, (long)p);
                    /* BLAM */
                }
            }

            fontSpaceRight = f->fontScale / 6; /* see dvitype.web §63 */
            fontSpaceLeft = -4 * fontSpaceRight;
            fontSpaceVert = 5 * fontSpaceRight;
            break;
	    
	    /*
	     * If a font definition comes up, we check whether we have
	     * seen this font number before (which can happen if we
	     * display the same page multiple times or if the fonts have
	     * been defined from the postamble). If this is not the case,
	     * we call the device-specific function to initialize the
	     * font.
	     */

            case D_FNTDEF1: case D_FNTDEF2: case D_FNTDEF3: case D_FNTDEF4:
                code = Dvi_ProcessFontDef(dviInterp, &dviInterp->fonts,
					  p, code, DVI_FMODE_DVI);
                break;

            case D_NOP:
                break;
                
            case D_BOP:
                /* Error */
                break;

            case D_EOP:
                /* done = 1; */
                break;

            case D_PUSH:
                if (dviInterp->stackPtr == dviInterp->stackSize) {
                    /*
                     * Dynamically enlarge the DVI stack
                     */
                }
                CONTEXT[1] = CONTEXT[0];
                dviInterp->stackPtr++;
                break;
                
            case D_POP:
                dviInterp->stackPtr--;
                break;

                /*
                 * A \special command is copied into a string and handed off
                 * to the device-specific function for processing.
                 */
                
            case D_XXX1: case D_XXX2: case D_XXX3: case D_XXX4:
            {
		(* dviInterp->specialProc)(dviInterp->procData, dviInterp,
					   code, HH, VV, (U32)p, code);
                code += p;
            }
            
            case D_PRE: case D_POST: case D_POSTPOST:
                break;

                /*
                 * We handle the mass D_SET_CHAR_x and D_FNT_NUM_x commands
                 * via the default case.
                 */
                
            default:
                if (o < D_SET1) {
                    goto finishSet;
                }
                if (o >= D_FNTNUM0 && o < D_FNT1) {
                    goto changeFont;
                }
        }
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 * GetFirstParameter --
 *
 *      Obtains a DVI opcode's first parameter (if any) from the DVI file.
 *      The parameter `codePtr' points at the parameter.
 *
 * Results:
 *      Returns a pointer to the DVI file location immediately following
 *      the parameter.
 *
 * Side effects:
 *      The value of the parameter in question is stored in the location
 *      pointed to by `contextPtr'.
 *
 * ------------------------------------------------------------------------
 */

static U8 *
GetFirstParameter (c, codePtr, valuePtr, contextPtr)
    int c;                      /* DVI command currently considered */
    U8 *codePtr;                /* Address of first parameter */
    S32 *valuePtr;              /* Value of first parameter */
    Dvi_StackFrame *contextPtr;	/* Current DVI execution stack frame */
{
    switch (c) {
        case D_SET1: case D_PUT1: case D_FNT1: case D_XXX1: case D_FNTDEF1:
            *valuePtr = DviGetU8(codePtr++);
            break;
        case D_SET2: case D_PUT2: case D_FNT2: case D_XXX2: case D_FNTDEF2:
            *valuePtr = DviGetU16(codePtr);
            codePtr += 2;
            break;
        case D_SET3: case D_PUT3: case D_FNT3: case D_XXX3: case D_FNTDEF3:
            *valuePtr = DviGetU24(codePtr);
            codePtr += 3;
            break;
        case D_RIGHT1: case D_W1: case D_X1: case D_DOWN1:case D_Y1:case D_Z1:
            *valuePtr = DviGetS8(codePtr++);
            break;
        case D_RIGHT2: case D_W2: case D_X2: case D_DOWN2:case D_Y2:case D_Z2:
            *valuePtr = DviGetS16(codePtr);
            codePtr += 2;
            break;
        case D_RIGHT3: case D_W3: case D_X3: case D_DOWN3:case D_Y3:case D_Z3:
            *valuePtr = DviGetS24(codePtr);
            codePtr += 3;
            break;
        case D_SET4: case D_PUT4: case D_FNT4: case D_XXX4: case D_FNTDEF4:
        case D_RIGHT4: case D_W4: case D_X4: case D_DOWN4:case D_Y4:case D_Z4:
        case D_SETRULE: case D_PUTRULE:
            *valuePtr = DviGetS32(codePtr);
            codePtr += 4;
            break;
        case D_NOP: case D_BOP: case D_EOP: case D_PUSH: case D_POP:
        case D_PRE: case D_POST: case D_POSTPOST:
            *valuePtr = 0;
            break;
        case D_W0:
            *valuePtr = contextPtr->w;
            break;
        case D_X0:
            *valuePtr = contextPtr->x;
            break;
        case D_Y0:
            *valuePtr = contextPtr->y;
            break;
        case D_Z0:
            *valuePtr = contextPtr->z;
            break;
        default:
            if (c < D_SET1) {
                *valuePtr = c;
                break;
            }
            *valuePtr = c - D_FNTNUM0;
    }
    return codePtr;
}

static void
stdGlyph (clientData, dviInterp, x, y, fontPtr, character, tfmWidthPtr,
          pixWidthPtr)
    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 *pixWidthPtr;           /* for returning character's pixel width */
{
    Tcl_DString *output = (Tcl_DString *)clientData;
    char buffer[1024];
    
    Dvi_FontGetGlyph(fontPtr, character, tfmWidthPtr, pixWidthPtr);
    sprintf(buffer, "GLYPH %ld %ld %ld %ld %s %ld (%c) %ld %ld\n",
            (long)H, (long)V, (long)x, (long)y, fontPtr->fontName,
            (long)character,
            (char)((character >= 32 && character <= 127) ? character : '?'),
            (long)*tfmWidthPtr, (long)*pixWidthPtr);
    if (output != 0) {
        Tcl_DStringAppend(output, buffer, -1);
    }
#if 1
    fputs(buffer, stderr);
#endif
}

static void
stdRule (clientData, dviInterp, x, y, height, width)
    ClientData clientData;
    Dvi_Interp *dviInterp;
    S32 x, y;
    S32 height, width;
{
    Tcl_DString *output = (Tcl_DString *)clientData;
    char buffer[1024];
    
    sprintf(buffer, "RULE %ld %ld %ld %ld %ld %ld\n",
            (long)H, (long)V, (long)x, (long)y, (long)height, (long)width);
    if (output != 0) {
        Tcl_DStringAppend(output, buffer, -1);
    }
#if 0
    fputs(buffer, stderr);
#endif
}

static int
stdFontDef (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;
{
    Tcl_DString *output = (Tcl_DString *)clientData;
    char buffer[1024];
    
    sprintf(buffer, "FONTDEF %ld %lu %lu %lu %.*s\n", (long)fontNum,
            (unsigned long)check, (unsigned long)fontScale,
            (unsigned long)designSize, (int)nameLen, name);
    Dvi_FontAdd(dviInterp, fontListPtr, fontNum,
		check, fontScale, designSize, nameLen, name);
    if (mode == DVI_FMODE_DVI && output != 0) {
        Tcl_DStringAppend(output, buffer, -1);
    }
#if 0
    fputs(buffer, stderr);
#endif
    return TCL_OK;
}

static int
stdSpecial (clientData, dviInterp, code, x, y, length, string)
    ClientData clientData;
    Dvi_Interp *dviInterp;
    U8 *code;
    S32 x, y;
    U32 length;
    const char *string;
{
    Tcl_DString *output = (Tcl_DString *)clientData;
    char buffer[1024];
    
    sprintf(buffer, "SPECIAL %ld %ld %p %ld %ld %lu %s\n",
            (long)HH, (long)VV, code, (long)x, (long)y,
            (unsigned long)length, string);
    if (output != 0) {
        Tcl_DStringAppend(output, buffer, -1);
    }
/*    fputs(buffer, stderr);*/
    return TCL_OK;
}
