/*
 * font.c --
 *
 *      This file implements access routines for fonts used in typesetting
 *      DVI files. The font-specific code is given elsewhere; this file
 *      only deals with the code needed to access font information in
 *      a manner not depending on the font format.
 *
 * Copyright © 1999 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>
 * See file COPYING for conditions on use and distribution.
 */

#include <string.h>
#include "dviInt.h"

#ifndef lint
static char rcsid[] VAR_UNUSED = "$Id: font.c,v 1.4 2000/06/29 11:01:38 lingnau Exp $";
#endif /* lint */

/*
 * The list of fonts currently in memory.
 */

typedef struct FontHead {
    struct FontHead *nextPtr;
    unsigned int resolution;
    Dvi_Font *fonts;
} FontHead;

static FontHead *fontHeadPtr = (FontHead *)0;

/*
 * An array holding the type-specific routines for handling various kinds
 * of font.
 */

#define MAX_FONTTYPES 10
static int fontTypeCount = 0;
static Dvi_FontTypeDesc fontTypes[MAX_FONTTYPES];

static FontHead *GetFontHead _ANSI_ARGS_((unsigned int resolution));

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CreateFontType --
 *
 *      Install font-handling routines for fonts of type type.
 *
 * Results:
 *      TCL_OK.
 *
 * Side effects:
 *      Pointers to the font-handling routines are stored in fontTypes[].
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_CreateFontType (type, name, loadProc, glyphProc, closeProc)
    Dvi_FontType type;
    char *name;
    Dvi_FontLoadProc *loadProc;
    Dvi_FontGlyphProc *glyphProc;
    Dvi_FontCloseProc *closeProc;
{
    fontTypes[type].name = name;
    fontTypes[type].loadProc = loadProc;
    fontTypes[type].glyphProc = glyphProc;
    fontTypes[type].closeProc = closeProc;
    fontTypeCount++;
    
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontAdd --
 *
 *      Adds a font to a DVI interpreter's font number translation table.
 *      The font is loaded into memory if it is not already in the font
 *      repository.
 *
 * Results:
 *      TCL_OK if everything went well, TCL_ERROR if the font could not
 *      be found.
 *
 * Side effects:
 *      Adds a Dvi_FontList record to the font list of dviInterp. Loads
 *      the font described by the parameters if necessary.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_FontAdd(dviInterp, fontListPtr, fontNum, check, fontScale, designSize,
	    nameLen, name)
    Dvi_Interp *dviInterp;      /* Interpreter for adding font to */
    Dvi_FontList **fontListPtr;	/* Address of font list */
    S32 fontNum;                /* Number of font to be installed */
    U32 check;                  /* Font checksum from DVI file */
    U32 fontScale;              /* Scaling factor from DVI file */
    U32 designSize;             /* Font design size from DVI file */
    size_t nameLen;             /* Length of font name from DVI file */
    char *name;                 /* Name of font in DVI file */
{
    Dvi_FontList *newItemPtr;
    Dvi_Font *fontPtr;

    fontPtr = Dvi_FontFind(dviInterp, check, fontScale,
                           designSize, nameLen, name);
    
    if (fontPtr == (Dvi_Font *)0) {
        fprintf(stderr, "Dvi_FontFind returned null pointer.\n");
        return TCL_ERROR;
    }
    
    newItemPtr = (Dvi_FontList *)ckalloc(sizeof(Dvi_FontList));
    if (newItemPtr == (Dvi_FontList *)0) {
        Dvi_FontFree(fontPtr);
        return TCL_ERROR;
    }

    newItemPtr->fontNum = fontNum;
    newItemPtr->fontPtr = fontPtr;
    newItemPtr->nextPtr = *fontListPtr;
    *fontListPtr = newItemPtr;
    
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontFind --
 *
 *      Looks for a font in the font repository and initializes the font
 *      (using Dvi_FontNew) if it isn't already there.
 *
 * Results:
 *      Returns a pointer to a newly allocated Dvi_Font or the null
 *      pointer if no memory was available.
 *
 * Side effects:
 *      The font is put into the common font repository if necessary.
 *
 * ------------------------------------------------------------------------
 */

Dvi_Font *
Dvi_FontFind(dviInterp, check, fontScale, designSize, nameLen, name)
    Dvi_Interp *dviInterp;      /* DVI interpreter */
    U32 check;                  /* Font checksum from DVI file */
    U32 fontScale;              /* Font scaling factor from DVI file */
    U32 designSize;             /* Font design size from DVI file */
    size_t nameLen;             /* Length of font name from DVI file */
    char *name;                 /* Name of font in DVI file */
{
    FontHead *headPtr;
    Dvi_Font *fontPtr;

    /*
     * Check whether the font is already in the list of fonts. If so,
     * increment its reference count and return a pointer to the
     * font's Dvi_Font structure.
     */

    headPtr = GetFontHead(dviInterp->xResolution);
    if (headPtr == (FontHead *)0) {
        return (Dvi_Font *)0;
    }

    for (fontPtr = headPtr->fonts; fontPtr != 0; fontPtr = fontPtr->nextPtr) {
        if (designSize == fontPtr->designSize
            && fontScale == fontPtr->fontScale
            && STR_EQ_N(name, fontPtr->fontName, nameLen)
            && fontPtr->fontName[nameLen] == '\0') {
            fontPtr->refCount += 1;
            return fontPtr;
        }
    }
    
    fontPtr = Dvi_FontNew(dviInterp, check, fontScale, designSize,
                          nameLen, name);
    
    if (fontPtr == (Dvi_Font *)0) {
        return (Dvi_Font *)0;
    }
    
    fontPtr->nextPtr = headPtr->fonts;
    headPtr->fonts = fontPtr;
    
    return fontPtr;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontNew --
 *
 *      Initializes a Dvi_Font structure for a new font. This is called
 *      from Dvi_FontFind when a document calls for some previously unseen
 *      font.
 *
 * Results:
 *      Returns a pointer to a newly allocated Dvi_Font or the null
 *      pointer if no memory was available.
 *
 * Side effects:
 *      The font is loaded from disk and a Dvi_Font structure is
 *      constructed.
 *
 * ------------------------------------------------------------------------
 */

Dvi_Font *
Dvi_FontNew(dviInterp, check, fontScale, designSize, nameLen, name)
    Dvi_Interp *dviInterp;      /* DVI interpreter */
    U32 check;                  /* Font checksum from DVI file */
    U32 fontScale;              /* Font scaling factor from DVI file */
    U32 designSize;             /* Font design size from DVI file */
    size_t nameLen;             /* Length of font name from DVI file */
    char *name;                 /* Name of font in DVI file */
{
    Dvi_Font *fontPtr;
    U32 fontMag;

    if ((fontPtr = (Dvi_Font *)ckalloc(sizeof(Dvi_Font))) == (Dvi_Font *)0) {
        return (Dvi_Font *)0;
    }

    /*
     * Get the font name and type from disk.
     */

    fontMag = (U32)((1000.0 * dviInterp->xConv * fontScale)
                    / (dviInterp->trueXConv * designSize) + 0.5);
    fontPtr->resolution = (U32)(fontMag/1000.0 * dviInterp->xResolution + 0.5);

    fontPtr->fileName = Dvi_FindFontFile(nameLen, name, &fontPtr->resolution,
                                         &fontPtr->type);
    if (fontPtr->fileName == (char *)0) {
        ckfree((char *)fontPtr);
        return (Dvi_Font *)0;
    }

    fontPtr->fontName = DviSaveStrN(name, nameLen);
    fontPtr->refCount = 1;
    fontPtr->check = check;
    fontPtr->fontScale = fontScale;
    fontPtr->designSize = designSize;

    /*
     * Now load the font itself, depending on the type.
     */

    if ((* fontTypes[fontPtr->type].loadProc)(dviInterp, fontPtr) != TCL_OK) {
        ckfree(fontPtr->fileName);
        ckfree(fontPtr->fontName);
        ckfree((char *)fontPtr);
        return (Dvi_Font *)0;
    }

    return fontPtr;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontGetGlyph --
 *
 *      Dispatch a request for font glyph information to the appropriate
 *      type-specific code.
 *
 * Results:
 *      A pointer to a Dvi_Glyph structure for the character in question
 *      or the null pointer if there is no glyph available. The
 *      device-independent width of the character is stored in *tfmWidthPtr
 *      and the device-dependent width is stored in *pixelWidthPtr.
 *
 * Side effects:
 *      None (but the type-specific code may cause some).
 *
 * ------------------------------------------------------------------------
 */

Dvi_Glyph *
Dvi_FontGetGlyph (dviFont, character, tfmWidthPtr, pixelWidthPtr)
    Dvi_Font *dviFont;		/* font to be searched */
    S32 character;		/* glyph to be found */
    S32 *tfmWidthPtr;		/* for returning width in DVI coordinates */
    S32 *pixelWidthPtr;		/* for returning width in device coordinates */
{
    return (* fontTypes[dviFont->type].glyphProc)(dviFont, character,
						  tfmWidthPtr, pixelWidthPtr);
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontFree --
 *
 *      Declares a font unused. If the number of times a font is used
 *      drops to zero, the font is removed at the next opportunity.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Decrements a font's reference count.
 *
 * ------------------------------------------------------------------------
 */

void
Dvi_FontFree(fontPtr)
    Dvi_Font *fontPtr;		/* Font to be freed */
{
    fontPtr->refCount -= 1;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontPurge --
 *      Removes all fonts whose reference count is zero.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      This procedure invokes the closeProc of every font in the
 *      repository whose reference count is zero, then removes that
 *      font from the list of fonts.
 *
 * ------------------------------------------------------------------------
 */

void
Dvi_FontPurge()
{
    FontHead *headPtr;

    for (headPtr = fontHeadPtr; headPtr != 0; headPtr = headPtr->nextPtr) {
	Dvi_Font *listPtr;
	Dvi_Font *prevPtr = (Dvi_Font *)0;
	Dvi_Font *nextPtr;

	for (listPtr = headPtr->fonts; listPtr != 0; listPtr = nextPtr) {
	    nextPtr = listPtr->nextPtr;
	    if (listPtr->refCount == 0) {
		(* fontTypes[listPtr->type].closeProc)(listPtr);
		ckfree((char *)listPtr);
		if (prevPtr == 0) {
		    headPtr->fonts = nextPtr;
		} else {
		    prevPtr->nextPtr = nextPtr;
		}
	    } else {
		prevPtr = listPtr;
	    }
	}
    }
}

/*
 * ------------------------------------------------------------------------
 *
 * GetFontHead --
 *
 *      Returns a pointer to the header structure for a font list at
 *      resolution `resolution'.
 *
 * Results:
 *      A FontHead pointer or a null pointer (in case of an error).
 *
 * Side effects:
 *      A FontHead structure is created and linked into the list of
 *      font headers if there wasn't one for the resolution in question.
 *
 * ------------------------------------------------------------------------
 */

static FontHead *
GetFontHead (resolution)
    unsigned int resolution;
{
    FontHead *headPtr;

    for (headPtr = fontHeadPtr; headPtr != 0; headPtr = headPtr->nextPtr) {
	if (resolution == headPtr->resolution) {
	    return headPtr;
	}
    }

    headPtr = (FontHead *)ckalloc(sizeof(FontHead));
    if (headPtr == (FontHead *)0) {
	return (FontHead *)0;
    }

    headPtr->resolution = resolution;
    headPtr->fonts = (Dvi_Font *)0;
    headPtr->nextPtr = fontHeadPtr;
    fontHeadPtr = headPtr;

    return headPtr;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontDump --
 *
 *      Lists information about a font pointed to by `fontPtr', for
 *      debugging purposes.
 *
 * Results:
 *      Information about the desired font.
 *
 * Side effects:
 *      None.
 *
 * ------------------------------------------------------------------------
 */

Tcl_Obj *
Dvi_FontDump(interp, fontPtr)
    Tcl_Interp *interp;		/* interpreter for error messages */
    Dvi_Font *fontPtr;		/* pointer to font to be dumped */
{
    Tcl_Obj *resultPtr = Tcl_NewListObj(0, (Tcl_Obj **)0);

    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewStringObj(fontPtr->fontName, -1));

    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewIntObj(fontPtr->resolution));
    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewLongObj(fontPtr->fontScale));
#if 0
    sprintf(buf, "%lu", (unsigned long)fontPtr->designSize);
#endif
    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewLongObj(fontPtr->designSize));

    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewStringObj(fontTypes[fontPtr->type].name,
					      -1));

    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewIntObj(fontPtr->refCount));

    Tcl_ListObjAppendElement(interp, resultPtr,
			     Tcl_NewStringObj(fontPtr->fileName, -1));
    return resultPtr;
}

#ifdef DVI_DEBUG
/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontDumpAll --
 *
 *      Lists information about all fonts currently available. This
 *      is only included when debugging is enabled.
 *
 * Results:
 *      Information about the fonts.
 *
 * Side effects:
 *      None.
 *
 * ------------------------------------------------------------------------ */

Tcl_Obj *
Dvi_FontDumpAll(interp)
    Tcl_Interp *interp;		/* Tcl interpreter for error reporting */
{
    FontHead *headPtr;
    Dvi_Font *fontPtr;
    Tcl_Obj *resultPtr = Tcl_NewListObj(0, (Tcl_Obj **)0);

    for (headPtr = fontHeadPtr; headPtr != 0; headPtr = headPtr->nextPtr) {
	Tcl_Obj *listPtr = Tcl_NewListObj(0, (Tcl_Obj **)0);

	Tcl_ListObjAppendElement(interp, listPtr,
				 Tcl_NewIntObj(headPtr->resolution));
	for (fontPtr = headPtr->fonts; fontPtr; fontPtr = fontPtr->nextPtr) {
	    Tcl_ListObjAppendElement(interp, listPtr,
				     Dvi_FontDump(interp, fontPtr));
	}
	Tcl_ListObjAppendElement(interp, resultPtr, listPtr);
    }
    return resultPtr;
}

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_FontDumpGlyph --
 *
 *      Display schematic representation of the glyph at position
 *      `character' in font `name' at size `fontSize', according to
 *      interpreter `dviInterp'.
 *
 * Results:
 *      A standard Tcl result, and the representation in the dynamic
 *      string `string'.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_FontDumpGlyph(dviInterp, fontSize, name, character, string)
    Dvi_Interp *dviInterp;	/* for resolution etc. */
    U32 fontSize;
    char *name;			/* Font name as in TeX */
    S32 character;		/* Character index to be displayed */
    Tcl_DString *string;	/* Result: Character representation */
{
    Dvi_Font *fontPtr;
    Dvi_Glyph *glyphPtr;
    S32 tfmWidth, pixelWidth;
    char buffer[40];		/* for numeric-to-string conversion */
    char *linePtr;
    U8 *bitmapPtr;
    unsigned int h, w;

    fontPtr = Dvi_FontFind(dviInterp, 0, fontSize, fontSize, strlen(name),
			   name);
    if (fontPtr == (Dvi_Font *)0) {
        return TCL_ERROR;
    }

    glyphPtr = Dvi_FontGetGlyph(fontPtr, character, &tfmWidth, &pixelWidth);
    if (glyphPtr == (Dvi_Glyph *)0) {
        return TCL_ERROR;
    }

    sprintf(buffer, "%u", glyphPtr->width);
    Tcl_DStringAppendElement(string, buffer);
    sprintf(buffer, "%u", glyphPtr->height);
    Tcl_DStringAppendElement(string, buffer);
    sprintf(buffer, "%d", glyphPtr->horizOffset);
    Tcl_DStringAppendElement(string, buffer);
    sprintf(buffer, "%ld", (long)tfmWidth);
    Tcl_DStringAppendElement(string, buffer);
    sprintf(buffer, "%ld", (long)pixelWidth);
    Tcl_DStringAppendElement(string, buffer);
    Tcl_DStringAppend(string, "\n", -1);

    /*
     * The bitmap has already been unpacked by Dvi_FontGetGlyph, above.
     * Copy it into the dynamic string, row by row (for efficiency).
     */

    linePtr = ckalloc((size_t)(glyphPtr->width + 2));
    strcpy(linePtr + glyphPtr->width, "\n");
    bitmapPtr = (U8 *)glyphPtr + sizeof(Dvi_Glyph);
    for (h = 0; h < glyphPtr->height; h++) {
        U8 mask = 0x80;
        U8 byte = *bitmapPtr++;
        for (w = 0; w < glyphPtr->width; w++) {
            linePtr[w] = (byte & mask) ? '*' : '.';
            mask >>= 1;
            if (mask == 0) {
                mask = 0x80;
                byte = *bitmapPtr++;
            }
        }
        if (glyphPtr->width % 8 == 0) {
            bitmapPtr--;
        }
        Tcl_DStringAppend(string, linePtr, -1);
    }
    ckfree(linePtr);
    return TCL_OK;
}
#endif /* DVI_DEBUG */

