/*
 * dviCode.c --
 *
 *      This file implements management routines for loaded DVI code.
 *
 * Copyright © 1999 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>
 * See file COPYING for conditions on use and distribution.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

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

#ifndef lint
static char rcsid[] VAR_UNUSED = "$Id: dviCode.c,v 1.3 2000/06/29 10:58:05 lingnau Exp $";
#endif /* lint */

static char opSizes[] = {
    1, 2, 3, 4, 5, 1, 2, 3,	/* D_SET1 (128) ... D_PUT3 (135) */
    4, 5, 0, 0, 0, 0, 0, 1,	/* D_PUT4 (136) ... D_RIGHT1 (143) */
    2, 3, 4, 0, 1, 2, 3, 4,	/* D_RIGHT2 (144) ... D_W4 (151) */
    0, 1, 2, 3, 4, 1, 2, 3,     /* D_X0 (152) ... D_DOWN3 (159) */
    4, 0, 1, 2, 3, 4, 0, 1,     /* D_DOWN4 (160) ... D_Z1 (167) */
    2, 3, 4, 0, 0, 0, 0, 0,     /* D_Z2 (168) ... D_FNTNUM6 (176) */
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 2, 3, 4, 7,     /* D_FNTNUM62 (232) ... D_XXX1 (239) */
    7, 7, 7, 6, 6, 6, 6, -1,	/* D_XXX2 (240) ... D_PRE (247) */
    -1, -1, -1, -1, -1, -1, -1, -1
};

static int PrescanFile _ANSI_ARGS_((Dvi_Code *codePtr,
				    Dvi_FileInfo *dviFileInfoPtr,
				    Dvi_CodeAnchorRegisterProc, ClientData));
static int AddHyperTeXAnchor _ANSI_ARGS_((Dvi_Code *codePtr,
					  char *namePtr, int nameLength,
					  unsigned int pageNum));

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeCreate --
 *
 *      Construct an appropriately initialized Dvi_Code object.
 *
 * Results:
 *      A pointer to the newly created Dvi_Code object or NULL in case of
 *      error.
 *
 * Side effects:
 *      Memory for a Dvi_Code object is allocated and initialized.
 *
 * ------------------------------------------------------------------------
 */

Dvi_Code *
Dvi_CodeCreate (num, den, mag, stackSize, pageCount)
    long num, den, mag;
    unsigned int stackSize;
    unsigned int pageCount;
{
    Dvi_Code *codePtr;

    codePtr = (Dvi_Code *)ckalloc(sizeof(Dvi_Code));
    if (codePtr == (Dvi_Code *)0) {
	return (Dvi_Code *)0;
    }

    codePtr->num = num;
    codePtr->den = den;
    codePtr->mag = mag;
    codePtr->stackSize = stackSize ? stackSize : DVI_MIN_STACK;
    codePtr->pageCount = pageCount;

    codePtr->pageTableSize = 0;
    codePtr->pageTable = (Dvi_PageInfo *)0;

    return codePtr;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeCreateFromFileInfo --
 *
 *      Create a newly initialized Dvi_Code object for the dviFileInfoPtr.
 *      This takes the parameters which otherwise must be specified on
 *      the Dvi_CodeCreate() command line from the file.
 *
 * Results:
 *      Returns a pointer to the newly created Dvi_Code object or
 *      NULL if an error has occurred.
 *
 * Side effects:
 *      The Dvi_Code object is allocated and initialized with
 *      values appropriate to the dviFileInfoPtr. The dviFileInfoPtr
 *      is prescanned for page numbers and \special commands.
 *
 * ------------------------------------------------------------------------
 */

Dvi_Code *
Dvi_CodeCreateFromFileInfo (dviFileInfoPtr, anchorRegisterProc,
			    anchorRegisterClientData)
    Dvi_FileInfo *dviFileInfoPtr;
    Dvi_CodeAnchorRegisterProc anchorRegisterProc;
    ClientData anchorRegisterClientData;
{
    Dvi_Code *codePtr;
    unsigned char *postamble;
    long num, den, mag;
    unsigned int stackSize, pageCount;

    Dvi_FileParameters(dviFileInfoPtr, &postamble, &num, &den, &mag,
		       &stackSize, &pageCount);
    codePtr = Dvi_CodeCreate(num, den, mag, stackSize, pageCount);

    if (codePtr) {
	PrescanFile(codePtr, dviFileInfoPtr, anchorRegisterProc,
		    anchorRegisterClientData);
    }
    return codePtr;
}

/*
 * ------------------------------------------------------------------------
 *
 * PrescanFile --
 *
 *      Scans a DVI file at high speed using a special rudimentary DVI
 *      interpreter. The object of the exercise is to locate the
 *      beginnings of pages and to find \special commands, which could
 *      nest across page boundaries (see Tom Rokicki's article in
 *      TUGboat). We should also scan for fonts but this isn't implemented
 *      yet.
 *
 *      This function makes it possible to read an incomplete DVI file
 *      from the front.
 *
 * Results:
 *      0 if an error has occurred, 1 otherwise.
 *
 * Side effects:
 *      None (for the time being)
 *
 * ------------------------------------------------------------------------
 */

enum { BUFFERSIZE_DEFAULT = 32 };

static int
PrescanFile (codePtr, dviFileInfoPtr, anchorRegisterProc, anchorClientData)
    Dvi_Code *codePtr;
    Dvi_FileInfo *dviFileInfoPtr;
    Dvi_CodeAnchorRegisterProc anchorRegisterProc;
    ClientData anchorClientData;
{
    U8 *code = dviFileInfoPtr->contents + 14;
    int ptIdx = 0;
    size_t strBufferSize = 0;
    char *strBufferPtr = (char *)0;

    code += *code + 1;
    while (*code == D_BOP) {
	/*
	 * Remember the position of this page. The page table is
	 * grown if necessary; if we don't have the correct size from
	 * the postamble we take a guess that might have to be corrected
	 * when more pages turn up than expected.
	 */

	if (ptIdx >= codePtr->pageTableSize) {
	    if (codePtr->pageTableSize == 0) {
		codePtr->pageTableSize = 32;
	    }
	    codePtr->pageTable
		= (Dvi_PageInfo *)ckrealloc((char *)codePtr->pageTable,
					    2 * codePtr->pageTableSize
					    * (sizeof(Dvi_PageInfo)));
	    if (codePtr->pageTable == (Dvi_PageInfo *)0) {
		return 0;
	    }
	    codePtr->pageTableSize *= 2;
	}
	codePtr->pageTable[ptIdx].code = code;

	/*
	 * Scan a single page (whose code begins 45 bytes beyond the
	 * D_BOP opcode. We use the opSizes table to skip most
	 * commands without actually looking at them closely.
	 */

	code += 45;
	while (*code != D_EOP) {
	    if (*code < 128) {
		code++;
	    } else {
		unsigned int parLength = 0;
		unsigned int fontNameLength;
		switch (opSizes[*code-128]) {
		case 5: /* rule command, 8 bytes of parameters */
		    code += 9; break;
		case 6: /* fontdef command */
		    code += (*code - D_FNTDEF1 + 1) + 13;
		    fontNameLength = code[0] + code[1];
#if 0
		    if (fontNameLength >= strBufferSize) {
			strBufferSize = strBufferSize != 0
			    ? 2 * strBufferSize : BUFFERSIZE_DEFAULT;
			strBufferPtr = realloc(strBufferPtr, strBufferSize);
		    }
		    strncpy(strBufferPtr, (char *)(code + 2),
			    fontNameLength - 2);
		    strBufferPtr[fontNameLength - 2] = '\0';
/*		    fprintf(stderr, "font: %s (%lu,%lu,%lu)\n",
		    strBufferPtr, *(int *)(code - 12), *(int *)(code - 8), *(int *)(code - 4));*/
#endif
		    code += fontNameLength + 1;
		    break;
		case 7: /* xxx command */
		    switch (*code - D_XXX1 + 1) {
		    case 4: parLength = *++code << 24;
		    case 3: parLength |= *++code << 16;
		    case 2: parLength |= *++code << 8;
		    case 1: parLength |= *++code;
		    }
		    if (*(code + 1) == 'h' &&
			strncmp(code + 1, "html:<a name=\"", 14) == 0) {
			if (parLength - 16 >= strBufferSize) {
			    strBufferSize = strBufferSize != 0
				? 2 * strBufferSize : BUFFERSIZE_DEFAULT;
			    strBufferPtr = realloc(strBufferPtr,
						   strBufferSize);
			}
			strncpy(strBufferPtr, (char *)(code + 15),
				parLength - 16);
			strBufferPtr[parLength - 16] = '\0';
			(* anchorRegisterProc)(anchorClientData,
					       strBufferPtr, ptIdx);
		    }
		    code += parLength + 1;
		    break;
		default:
		    code += opSizes[*code-128] + 1;
		    break;
		case -1:
		    fprintf(stderr, "invalid DVI opcode %d at %p\n",
			    *code, code);
		    return 0;
		    break;
		}
	    }
	}
	code++;
	ptIdx++;
    }
    codePtr->pageCount = ptIdx;

    return 1;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeGetPageNumbers --
 *
 *      Collects the ten TeX `page numbers' of the absolute page numbered
 *      absPageNo into memory pointed to by pageNoPtr. (It is the caller's
 *      responsibility that there is enough memory available at that
 *      address.)
 *
 *      This is used in the Dvi_CodeFindTeXPage() function below and also
 *      in the Tcl interface, where a list of page numbers in the file
 *      is constructed and made available to Tcl.
 *
 * Results:
 *      1 if the page numbers could be retrieved; 0 if there is no page
 *      absPageNo in the file.
 *
 * Side effects:
 *      See above.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_CodeGetPageNumbers (codePtr, absPageNo, pageNoPtr)
    Dvi_Code *codePtr;
    unsigned int absPageNo;
    long *pageNoPtr;
{
    U8 *code;
    int i;

    /*
     * Check whether the page exists at all.
     */

    if (absPageNo >= codePtr->pageCount) {
	return 0;
    }

    /*
     * Get the page numbers.
     */

    code = codePtr->pageTable[absPageNo].code + 1;
    for (i = 0; i < 10; i++) {
	*pageNoPtr++ = DviGetS32(code);
	code += 4;
    }
    return 1;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeFindTeXPage --
 *
 *      Locates the absolute page number corresponding to the Dvi_PageSpec
 *      (see below for an explanation of Dvi_PageSpec).
 *
 * Results:
 *      The absolute page number (range 0 ... 65535 by definition of the
 *      DVI file) or -1 if no page in the DVI code corresponds to the
 *      given Dvi_PageSpec.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_CodeFindTeXPage (codePtr, pageSpecPtr)
    Dvi_Code *codePtr;
    Dvi_PageSpec *pageSpecPtr;
{
    if (pageSpecPtr->countersUsed == DVI_PS_ABSOLUTE) {
	/*
	 * This is already an absolute page number -- check whether
	 * it is in the valid range for this piece of code.
	 */

	int pageNo = pageSpecPtr->number[0];
	
	return (pageNo < 0 || pageNo >= codePtr->pageCount) ? -1 : pageNo;
    } else {
	/*
	 * TeX-style page numbers. Get the page numbers from the code
	 * and match them against the Dvi_PageSpec.
	 */

	int occurrenceCount;	/* counts occurrences of page number */
	unsigned int pageNo;

	occurrenceCount = pageSpecPtr->occurrences;
	for (pageNo = 0; pageNo < codePtr->pageCount; pageNo++) {
	    int match = 1;	/* is page still under consideration? */
	    long count[10];
	    int i;

	    Dvi_CodeGetPageNumbers(codePtr, pageNo, count);
	    for (i = 0; match && i < pageSpecPtr->countersUsed; i++) {
		match &= DVI_PS_IGNORE(pageSpecPtr, i)
		    || pageSpecPtr->number[i] == count[i];
	    }
	    if (match && --occurrenceCount == 0) {
		return pageNo;
	    }
	}
    }
    return -1;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeFindCodeForPage --
 *
 *      Returns a pointer to the actual DVI code corresponding to
 *      absolute page absPageNo, or NULL if absPageNo is out of range.
 *
 * Side effects:
 *      None.
 *
 * ------------------------------------------------------------------------
 */

U8 *
Dvi_CodeFindCodeForPage (codePtr, absPageNo)
    Dvi_Code *codePtr;
    unsigned int absPageNo;
{
    if (absPageNo >= codePtr->pageCount) {
	return (U8 *)0;
    }
    return codePtr->pageTable[absPageNo].code;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeGetPageSpec --
 *
 *      Converts page number specification string to Dvi_PageSpec.
 *
 * Results:
 *      The return value is normally 1. If the string is
 *      improperly formed then 0 is returned.
 *
 * Side effects:
 *      *pageSpecPtr is filled in according to the string.
 *
 * ------------------------------------------------------------------------
 */

#define PS_ABS          '='     /* leader for absolute page number */
#define PS_SEP          '.'     /* separator in multi-number page numbers */
#define PS_NUMBER       '#'     /* separator between page number and multi */
#define PS_WILD         '*'     /* page number wildcard */

#define PS_DONT_CARE(p, i)      ((p)->careVector &= ~(1 << (i)))
#define PS_DO_CARE(p, i)        ((p)->careVector |= (1 << (i)))

int
Dvi_CodeGetPageSpec(specString, pageSpecPtr)
    char *specString;		/* Page number specification string */
    Dvi_PageSpec *pageSpecPtr;	/* Page specification to be filled in */
{
    char *string = specString;	/* Pointer for parsing the specification */
				/* (We keep the original for error msgs) */
    int count;			/* Number of counters used */

    /*
     * An `absolute' (non-TeX) page number starts out with PS_ABS
     * (normally `='). The convention is to put DVI_PS_ABSOLUTE into
     * `countersUsed' and the absolute page number into `number[0]'.
     */

    if (*string == PS_ABS) {
	if (*(string + 1) == '\0') {
	    return 0;
	}
	if ((pageSpecPtr->number[0] = strtol(string + 1, (char **)0, 10)) < 0
	    || pageSpecPtr->number[0] > 65535) {
	    return 0;
	}
	pageSpecPtr->countersUsed = DVI_PS_ABSOLUTE;
	return 1;
    }

    /*
     * Otherwise, we have to deal with a TeX page number. The page
     * number consists of up to ten parts; each of these parts corresponds
     * to one of the ten counters exported into the DVI file with each BOP
     * command. Each part can either be PS_WILD (`*'), meaning `don't
     * care', or a signed 32-bit value (the page number). The TeX page
     * number may optionally be followed by a PS_NUMBER (`#') character
     * and an unsigned int value. If this value, say i, is given, it
     * stands for the i-th occurrence of the given page number in the DVI
     * file. This allows us to deal properly with things like LaTeX,
     * which can cause the same page number to be used several times over.
     * The number of parts given in the specification is assigned to
     * `countersUsed'; the unused parts up to 10 are implicitly ignored when
     * searching for a specific page.
     */

    pageSpecPtr->countersUsed = 0;
    pageSpecPtr->careVector = 0;
    for (count = 0; count < 10; count++) {
        long n;
        
        /*
         * Check for multiplicity (invalid at start of string) or
         * end of the string.
         */
        
        if (*string == '\0' || *string == PS_NUMBER) {
            if (count == 0) {
                return 0;
            }
            if (*string == PS_NUMBER) {
                if ((pageSpecPtr->occurrences = strtoul(string+1, &string, 10))
                    > 65535) {
                    return 0;
		}
                break;
            } else {
                pageSpecPtr->occurrences = 1;
            }
            break;
        }
        
        /*
         * Check whether a wildcard character (PS_WILD) or valid number
         * is coming up. Anything that is outside the S32 range is an error;
         * on 32-bit machines it may be difficult to detect this because
         * the borderline values are what strtol() returns in case of
         * over/underflow; we check this by looking at errno.
         */
        
        if (!isdigit(*string) && *string != '*' && *string != '-') {
            return 0;
        }
        
        errno = 0;
        if (*string == PS_WILD) {
	    pageSpecPtr->number[count] = 0;
            PS_DONT_CARE(pageSpecPtr, count);
            string++;
        } else if ((n = strtol(string, &string, 10)) <= MAX_S32
                   && n >= MIN_S32
                   && errno != ERANGE) {
            pageSpecPtr->number[count] = (S32)n;
            PS_DO_CARE(pageSpecPtr, count);
        } else {
            return 0;
        }
        
        pageSpecPtr->countersUsed++;
        if (*string != '\0' && *string != PS_SEP && *string != PS_NUMBER) {
            return 0;
        }
        if (*string == PS_SEP) {
            string++;
        }
    }
    if (*string != '\0') {
        return 0;
    }
    return 1;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CodeDestroy --
 *
 *      Deletes a Dvi_Code object. Also frees memory taken up by page
 *      table etc.
 *
 * ------------------------------------------------------------------------
 */

void
Dvi_CodeDestroy (codePtr)
    Dvi_Code *codePtr;
{
    if (codePtr->pageTable) {
	ckfree((char *)codePtr->pageTable);
    }
    ckfree((char *)codePtr);
}
