/*
 * fontTFM.c --
 *
 *      This file implements access routines for TFM files. We use TFM
 *      files if there are no VF or PK files giving glyph information
 *      for a font, to find out how much space to leave for characters
 *      in the font in question. The code in this file is based heavily
 *      on Donald E. Knuth's dvitype program.
 *
 * Copyright © 1999 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>
 * See file COPYING for conditions on use and distribution.
 */

#include <math.h>
#include "dvi.h"
#include "dviInt.h"

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

/*
 * This structure describes the information which is specific to the
 * TFM file belonging to a font.
 */

typedef struct TfmInfo {
    S32 firstChar, lastChar;	/* Range of characters in the file */
    S32 *tfmWidth;		/* Table of device-independent widths */
    S32 *pixelWidth;		/* Table of device-dependent widths */
} TfmInfo;

/*
 * Prototypes for procedures referenced only in this file:
 */

static int TfmLoad _ANSI_ARGS_((Dvi_Interp *dviInterp, Dvi_Font *dviFont));
static Dvi_Glyph *TfmGlyph _ANSI_ARGS_((Dvi_Font *dviFont, S32 character,
					S32 *tfmWidthPtr, S32 *pixelWidthPtr));
static int TfmClose _ANSI_ARGS_((Dvi_Font *dviFont));

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CreateFontType_TFM --
 *
 *      Installs the routines for handling TFM fonts.
 *
 * Results:
 *      TCL_OK if the TFM font routines could be installed properly,
 *      TCL_ERROR otherwise.
 *
 * Side effects:
 *      The TFM font handling routines are installed.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_CreateFontType_TFM ()
{
    return Dvi_CreateFontType(dvi_font_tfm, "tfm", TfmLoad, TfmGlyph,
			      TfmClose);
}

/*
 * ------------------------------------------------------------------------
 *
 * TfmLoad --
 *
 *      Load a TFM file and scale the metrics appropriately.
 *
 * Results:
 *      Returns TCL_OK if the font file could be successfully loaded
 *      and the metrics scaled. If the file couldn't be loaded, there
 *      was no memory available for the font information record or width
 *      tables or the TFM file held invalid data, TCL_ERROR is returned.
 *
 * Side effects:
 *      The TFM file corresponding to dviFont is loaded into memory
 *      and the character widths scaled appropriately for the desired
 *      magnification according to the conversion parameter from dviInt.
 *
 * ------------------------------------------------------------------------
 */

static int
TfmLoad (dviInterp, dviFont)
    Dvi_Interp *dviInterp;
    Dvi_Font *dviFont;
{
    U8 b0, b1, b2, b3;		/* Bytes of a TFM information word */
    U8 *addr;			/* Working pointer for reading TFM info */
    S32 alpha, beta, z;		/* Variables for scaling TFM widths */
    S32 inWidth[256];		/* Input width temporary storage */
    int lh;			/* Length of TFM file header, in words */
    int nw;			/* Number of distinct widths in font */
    int k;			/* Loop index */
    TfmInfo *info;		/* TFM-specific font information */

    /*
     * Allocate memory for font-specific information.
     */

    info = (TfmInfo *)ckalloc(sizeof(TfmInfo));
    if (info == (TfmInfo *)0) {
	return TCL_ERROR;
    }

    /*
     * Read the TFM file into memory; bail out if unsuccessful
     */

    dviFont->bytes = Dvi_LoadFileBinary(dviFont->fileName);
    if (dviFont->bytes == (U8 *)0) {
	ckfree((char *)info);
	return TCL_ERROR;
    }

    /*
     * Skip the TFM file header, reading font parameters while doing so.
     * It is an error for a TFM file if the number of widths is purported
     * to be zero or greater than 256.
     */

#define ReadTFMWord() \
    b0 = *addr++, b1 = *addr++, b2 = *addr++, b3 = *addr++

    addr = dviFont->bytes;
    ReadTFMWord(); lh = 256 * b2 + b3;
    ReadTFMWord();
    info->firstChar = 256 * b0 + b1; info->lastChar = 256 * b2 + b3;
    ReadTFMWord(); nw = 256 * b0 + b1;

    if (nw == 0 || nw > 256) {
	goto error;
    }

    for (k = 1; k <= 3 + lh; ++k) {
	ReadTFMWord();
    }

    info->tfmWidth = (S32 *)ckalloc(sizeof(S32)
				    * (info->lastChar-info->firstChar+1));
    info->pixelWidth = (S32 *)ckalloc(sizeof(S32)
				      * (info->lastChar-info->firstChar+1));

    /*
     * After the header, a TFM file contains a 4-byte packet of information
     * for each character. We are only interested in the first byte of
     * each packet, which is an index into the width table giving the
     * device-independent width of the character.
     */

    for (k = info->firstChar; k <= info->lastChar; k++) {
	ReadTFMWord();
	if (b0 > nw) {
	    goto error;
	}
	info->tfmWidth[k - info->firstChar] = b0;
    }

    /*
     * Right after the character information array, there is the width
     * table, which lists the distinct device-independent widths occurring
     * in this file. We read this table and scale the widths according to
     * the font magnification. The algorithm here is from Knuth's dvitype
     * program and goes to great lengths to avoid overflow and other
     * kinds of nastiness.
     */

    z = dviFont->fontScale;
    alpha = 16;
    while (z >= 040000000) {
	z /= 2; alpha *= 2;
    }
    beta = 256 / alpha; alpha *= z;
    for (k = 0; k < nw; k++) {
	ReadTFMWord();
	inWidth[k] = (((((b3*z) / 0400) + (b2*z)) / 0400) + (b1*z)) / beta;
	if (b0 > 0) {
	    if (b0 < 255) {
		goto error;
	    }
	    inWidth[k] -= alpha;
	}
    }

    /*
     * Now we replace the width indices in info->tfmWidth[] by the
     * properly scaled device-independent width values which we just
     * constructed. We calculate the device-dependent (pixel) widths by
     * simply rounding the device-independent widths to the nearest pixel.
     * This is the only feasible thing to do when reading TFM files; the
     * pixel widths are encoded separately in PK files and may differ
     * slightly from the rounded device-independent widths for aesthetic
     * reasons.
     */

    for (k = info->firstChar; k <= info->lastChar; k++) {
	info->tfmWidth[k - info->firstChar]
	    = inWidth[info->tfmWidth[ k - info->firstChar ] ];
	info->pixelWidth[k - info->firstChar]
	    = floor(info->tfmWidth[k-info->firstChar]*dviInterp->xConv + 0.5);
    }
    dviFont->fontData = (ClientData)info;
    return TCL_OK;

    /*
     * If there has been trouble along the way, we take care to free up
     * all the dynamic memory that we've been allocating.
     */

 error:
    if (info->tfmWidth) {
	ckfree((char *)info->tfmWidth); info->tfmWidth = (S32 *)0;
    }
    if (info->pixelWidth) {
	ckfree((char *)info->pixelWidth); info->pixelWidth = (S32 *)0;
    }
    ckfree((char *)info);
    ckfree((char *)dviFont->bytes); dviFont->bytes = (U8 *)0;
    return TCL_ERROR;
}

/*
 * ------------------------------------------------------------------------
 *
 * TfmGlyph --
 *
 *      Return a pointer to a character's glyph as well as its widths.
 *
 * Results:
 *      A pointer to a Dvi_Glyph structure describing this character.
 *      The device-independent width of the character is placed in
 *      *tfmWidthPtr, and the device-dependent width in *pixelWidthPtr.
 *      Actually, this function always returns the null pointer since there
 *      is no glyph for TFM characters.
 *
 * Side effects:
 *      None.
 *
 * ------------------------------------------------------------------------
 */

static Dvi_Glyph *
TfmGlyph (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 */
{
    TfmInfo *info = (TfmInfo *)dviFont->fontData;
    if (character < info->firstChar || character > info->lastChar
	    || info->tfmWidth[character - info->firstChar] == 0) {
	*tfmWidthPtr = *pixelWidthPtr = 0;
	return (Dvi_Glyph *)0;
    }
    *tfmWidthPtr = info->tfmWidth[character - info->firstChar];
    *pixelWidthPtr = info->pixelWidth[character - info->firstChar];
    return (Dvi_Glyph *)0;
}

/*
 * ------------------------------------------------------------------------
 *
 * TfmClose --
 *
 *      Removes font-specific information from memory when the font is
 *      deleted.
 *
 * Results:
 *      TCL_OK.
 *
 * Side effects:
 *      The font-specific information and width tables are erased from
 *      memory.
 *
 * ------------------------------------------------------------------------
 */

static int
TfmClose(dviFont)
    Dvi_Font *dviFont;
{
    TfmInfo *info = (TfmInfo *)dviFont->fontData;

    ckfree((char *)info->tfmWidth);
    ckfree((char *)info->pixelWidth);
    ckfree((char *)info);
    return TCL_OK;
}
