/*
 * fontPK.c --
 *
 *      This file implements support for PK fonts. It is based on code
 *      written for Anselm Lingnau's ASCREEN previewer for the Atari ST,
 *      which in turn is based on Tom Rokicki's pktype program.
 *
 * Copyright © 1999 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>
 * See file COPYING for conditions on use and distribution.
 */

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

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

typedef struct PkCharInfo {
    S32 tfmWidth;		/* Device-independent width of character */
    S32 pixelWidth;		/* Horizontal escapement */
    U32 offset;			/* Character glyph offset in file */
    Dvi_Glyph *glyphPtr;	/* Unpacked glyph for character */
} PkCharInfo;

typedef struct PkInfo {
    S32 minChar, maxChar;	/* Range of characters in file */
    PkCharInfo *charInfoPtr;	/* Per-character information */
} PkInfo;

/*
 * PK file `commands'.
 */

#define PK_PRE 247
#define PK_ID 89

#define RoundUp8(k) ((((k) + 8) - 1) / 8)

static U8 *nybblePtr;
static int firstNybble;
static U32 dynF;
static U32 repeatCount;
static U8 *bitmapPtr;

/*
 * Prototypes for procedures referenced only in this file:
 */
static int CheckPreamble _ANSI_ARGS_((U8 *codePtr, U32 checkSumDVI));
static PkInfo *MakeInfo _ANSI_ARGS_((Dvi_Font *dviFont));

static int PkLoad _ANSI_ARGS_((Dvi_Interp *dviInterp, Dvi_Font *dviFont));
static Dvi_Glyph *PkGlyph _ANSI_ARGS_((Dvi_Font *dviFont, S32 character,
				       S32 *tfmWidthPtr, S32 *pixelWidthPtr));
static Dvi_Glyph *GlyphHeader _ANSI_ARGS_((U8 *codePtr, U8 **newCodePtrPtr));
static Dvi_Glyph *PkBitmap _ANSI_ARGS_((U8 *codePtr));
static void NewNybble _ANSI_ARGS_((U8 *newNybblePtr));
static U32 GetNybble _ANSI_ARGS_((void));
static U32 PackedNumber _ANSI_ARGS_((void));
static U32 SetBits _ANSI_ARGS_((U32 position, U32 bits));
static Dvi_Glyph *PkRunCount _ANSI_ARGS_((U8 *codePtr));

static int PkClose _ANSI_ARGS_((Dvi_Font *dviFont));

/*
 * ------------------------------------------------------------------------
 *
 * Dvi_CreateFontType_PK --
 *
 *      Installs the routines for handling PK fonts.
 *
 * Results:
 *      TCL_OK if the PK font routines could be installed properly,
 *      TCL_ERROR otherwise.
 *
 * Side effects:
 *      The PK font handling routines are installed.
 *
 * ------------------------------------------------------------------------
 */

int
Dvi_CreateFontType_PK ()
{
    return Dvi_CreateFontType(dvi_font_pk, "pk", PkLoad, PkGlyph, PkClose);
}

static int
CheckPreamble (codePtr, checkSumDVI)
    U8 *codePtr;		/* Pointer to PK file */
    U32 checkSumDVI;		/* Font checksum from DVI file */
{
    U32 checkSumPK;		/* Font checksum in PK file */

    /*
     * Check the identification bytes and skip the header comment.
     */

    if (*codePtr != PK_PRE || *(codePtr+1) != PK_ID) {
	return TCL_ERROR;
    }
    codePtr += 2; codePtr += *codePtr + 5;

    /*
     * Compare the checksums. (This doesn't do anything right now.)
     */

    checkSumPK = (codePtr[0] << 24) | (codePtr[1] << 16) | (codePtr[2] << 8)
	| codePtr[3];
    if (checkSumDVI && checkSumPK && checkSumDVI != checkSumPK) {
	/*
	 * Warn about checksum mismatch.
	 */
	;
    }
    return TCL_OK;
}

static PkInfo *
MakeInfo (dviFont)
    Dvi_Font *dviFont;
{
    U8 *basePtr = dviFont->bytes; /* beginning of PK file */
    U8 *codePtr = basePtr;
    U8 *helpPtr;
    U32 charNum;
    U32 minChar = (1UL << 31);
    U32 maxChar = 0;
    PkInfo *infoPtr;
    PkCharInfo *charInfoPtr;
    PkCharInfo *charPtr;
    int tfmError = 0;
    S32 alpha = 16;
    S32 beta;
    S32 z = dviFont->fontScale;

    while (z >= 040000000L) {
	z /= 2; alpha *= 2;
    }
    beta = 256 / alpha; alpha *= z;

#define tfmcalc(b0, b1, b2, b3) \
	(((b0) > 0) ? \
		((b0) < 255 ? tfmError = 1, 0 \
			: ((((b3)*z)/256+(b2)*z)/256+(b1)*z)/beta - alpha ) \
		: ((((b3)*z)/256+(b2)*z)/256+(b1)*z)/beta )

    if ((infoPtr = (PkInfo *)ckalloc(sizeof(PkInfo))) == (PkInfo *)0) {
	return (PkInfo *)0;
    }

    codePtr += codePtr[2] + 19;
    helpPtr = codePtr;

    while (*codePtr != 0xf5) {
	U8 size = *codePtr & 0x07;
	if (*codePtr >= 0xf0) {
	    U32 k = 0;
	    switch (*codePtr++) {
		/*
		 * Ignore PK `special' commands. There are four different
		 * commands, followed by a 1-byte, 2-byte, ..., 4-byte
		 * length indication for the actual `special' string,
		 * respectively. We just skip over them, using an
		 * ingenious method called Duff's Device, after Tom Duff.
		 */

	    case 0xf3: k |= *codePtr++ << 24;
	    case 0xf2: k |= *codePtr++ << 16;
	    case 0xf1: k |= *codePtr++ << 8;
	    case 0xf0: k |= *codePtr++;
		codePtr += k;
		continue;

		/*
		 * A `numspecial' command is also ignored, as is a PK no-op
		 * command.
		 */

	    case 0xf4:
		codePtr += 4;
		continue;
	    case 0xf6:
		continue;
	    }
	}

	/*
	 * Everything else either can't occur or signifies the start
	 * of a character packet. Character packets come in three
	 * flavours, `short', `extended short' and `long'; they
	 * differ from each other by the amount the character
	 * parameters are omitted/shortened.
	 */

	if (size <= 3) {
	    /*
	     * A `short' character.
	     */

	    charNum = codePtr[2];
	    codePtr += ((codePtr[0] & 0x3) << 8) + codePtr[1] + 3;
	} else if (size <= 6) {
	    /*
	     * An `extended short' character.
	     */

	    charNum = codePtr[3];
	    codePtr += ((codePtr[0] & 0x3) << 16) + (codePtr[1] << 8)
		+ codePtr[2] + 4;
	} else {
	    /*
	     * A `long' character.
	     */

	    charNum = (S32)((codePtr[5] << 24) + (codePtr[6] << 16)
			    + (codePtr[7] << 8) + codePtr[8]);
	    codePtr += (codePtr[1] << 24) + (codePtr[2] << 16)
		+ (codePtr[3] << 8) + codePtr[4] + 9;
	}

	if (charNum > maxChar) {
	    maxChar = charNum;
	}
	if (charNum < minChar) {
	    minChar = charNum;
	}
    }

    charInfoPtr = (PkCharInfo *)ckalloc((maxChar - minChar + 1)
					* sizeof(PkCharInfo));
    if (charInfoPtr == (PkCharInfo *)0) {
	ckfree((char *)infoPtr);
	return (PkInfo *)0;
    }

    memset(charInfoPtr, 0x00, (maxChar - minChar + 1) * sizeof(PkCharInfo));

    codePtr = helpPtr;
    while (*codePtr != 0xf5) {
	U8 size = *codePtr & 0x7;
	if (*codePtr >= 0xf0) {
	    U32 k = 0;
	    switch (*codePtr++) {
	    case 0xf3: k |= *codePtr++ << 24;
	    case 0xf2: k |= *codePtr++ << 16;
	    case 0xf1: k |= *codePtr++ << 8;
	    case 0xf0: k |= *codePtr++;
		codePtr += k;
		continue;
	    case 0xf4:
		codePtr += 4;
		continue;
	    case 0xf6:
		continue;
	    }
	}

	if (size <= 3) {
	    /*
	     * A `short' character.
	     */

	    charPtr = charInfoPtr - minChar + codePtr[2];
	    charPtr->offset = codePtr - basePtr;
	    charPtr->pixelWidth = codePtr[6];
	    charPtr->tfmWidth = tfmcalc(0, codePtr[3], codePtr[4], codePtr[5]);
	    charPtr->glyphPtr = (Dvi_Glyph *)0;
	    codePtr += ((codePtr[0] & 0x3) << 8) + codePtr[1] + 3;
	} else if (size <= 6) {
	    /*
	     * An `extended short' character.
	     */

	    charPtr = charInfoPtr - minChar + codePtr[3];
	    charPtr->offset = codePtr - basePtr;
	    charPtr->pixelWidth = (codePtr[7] << 8) + codePtr[8];
	    charPtr->tfmWidth = tfmcalc(0, codePtr[4], codePtr[5], codePtr[6]);
	    charPtr->glyphPtr = (Dvi_Glyph *)0;
	    codePtr += ((codePtr[0] & 0x3) << 16) + (codePtr[1] << 8)
		+ codePtr[2] + 4;
	} else {
	    /*
	     * A `long' character.
	     */

	    charPtr = charInfoPtr - minChar
		+ ((codePtr[5] << 24) | (codePtr[6] << 16)
		   | (codePtr[7] << 8) | codePtr[8]);
	    charPtr->offset = codePtr - basePtr;
	    charPtr->pixelWidth = (S32)((codePtr[13] << 24) | (codePtr[14]<<16)
					| (codePtr[15] << 8) | codePtr[16]);
	    charPtr->tfmWidth = tfmcalc(codePtr[9], codePtr[10], codePtr[11],
					codePtr[12]);
	    charPtr->glyphPtr = (Dvi_Glyph *)0;
	    codePtr += (codePtr[1] << 24) + (codePtr[2] << 16)
		+ (codePtr[3] << 8) + codePtr[4] + 9;
	}
    }

    if (tfmError) {
	ckfree((char *)charInfoPtr);
	ckfree((char *)infoPtr);
	return (PkInfo *)0;
    }

    infoPtr->minChar = minChar;
    infoPtr->maxChar = maxChar;
    infoPtr->charInfoPtr = charInfoPtr;
    return infoPtr;
}

static int
PkLoad (dviInterp, dviFont)
    Dvi_Interp *dviInterp;
    Dvi_Font *dviFont;
{
    PkInfo *infoPtr;

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

    dviFont->bytes = Dvi_LoadFileBinary(dviFont->fileName);
    if (dviFont->bytes == (U8 *)0) {
	if (dviInterp != (Dvi_Interp *)0) {
	    Tcl_SetResult(dviInterp->interp, "couldn't load file", TCL_STATIC);
	}
	return TCL_ERROR;
    }

    if (CheckPreamble(dviFont->bytes, dviFont->check) != TCL_OK) {
	if (dviInterp != (Dvi_Interp *)0) {
	    Tcl_SetResult(dviInterp->interp, "error in PK preamble",
			  TCL_STATIC);
	}
	ckfree((char *)dviFont->bytes);
	return TCL_ERROR;
    }

    infoPtr = MakeInfo(dviFont);
    if (infoPtr == (PkInfo *)0) {
	if (dviInterp != (Dvi_Interp *)0) {
	    Tcl_SetResult(dviInterp->interp, "error constructing font info",
			  TCL_STATIC);
	}
	ckfree(dviFont->bytes); dviFont->bytes = (U8 *)0;
	return TCL_ERROR;
    }
    dviFont->fontData = (ClientData)infoPtr;
    return TCL_OK;
}

static Dvi_Glyph *
PkGlyph (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 */
{
    PkInfo *infoPtr = (PkInfo *)dviFont->fontData;
    Dvi_Glyph *glyphPtr;
    U8 *codePtr;

    if (character < infoPtr->minChar || character > infoPtr->maxChar) {
	*tfmWidthPtr = *pixelWidthPtr = 0;
	return (Dvi_Glyph *)0;
    }

    *tfmWidthPtr = (infoPtr->charInfoPtr)[character-infoPtr->minChar].tfmWidth;
    *pixelWidthPtr = (infoPtr->charInfoPtr)[character-infoPtr->minChar]
	.pixelWidth;

    glyphPtr = (infoPtr->charInfoPtr)[character - infoPtr->minChar].glyphPtr;
    if (glyphPtr != (Dvi_Glyph *)0) {
	return glyphPtr;
    }
    codePtr = dviFont->bytes
	+ (infoPtr->charInfoPtr)[character-infoPtr->minChar].offset;
    glyphPtr = ((*codePtr & 0xf0) == 0xe0)
	? PkBitmap(codePtr) : PkRunCount(codePtr);
    (infoPtr->charInfoPtr)[character - infoPtr->minChar].glyphPtr = glyphPtr;
    return glyphPtr;
}

static Dvi_Glyph *
GlyphHeader(codePtr, newCodePtrPtr)
    U8 *codePtr;
    U8 **newCodePtrPtr;
{
    Dvi_Glyph *glyphPtr;
    U32 width, height;
    S32 horizOffset, vertOffset;
    size_t size;

    switch (*codePtr & 0x7) {
    case 7:
	codePtr += 21;
	width = *codePtr++ << 24, width |= *codePtr++ << 16,
	    width |= *codePtr++ << 8, width |= *codePtr++;
	height = *codePtr++ << 24, height |= *codePtr++ << 16,
	    height |= *codePtr++ << 8, height |= *codePtr++;
	horizOffset = (S8)*codePtr++ << 24, horizOffset |= *codePtr++ << 16,
	    horizOffset |= *codePtr++ << 8, horizOffset |= *codePtr++;
	vertOffset = (S8)*codePtr++ << 24, vertOffset |= *codePtr++ << 16,
	    vertOffset |= *codePtr++ << 8, vertOffset |= *codePtr++;
	break;
    case 0: case 1: case 2: case 3:
	codePtr += 7;
	width = *codePtr++, height = *codePtr++;
	horizOffset = (S8)*codePtr++, vertOffset = (S8)*codePtr++;
	break;
    case 4: case 5: case 6:
	codePtr += 9;
	width = *codePtr++ << 8, width |= *codePtr++;
	height = *codePtr++ << 8, height |= *codePtr++;
	horizOffset = (S8)*codePtr++ << 8, horizOffset |= *codePtr++;
	vertOffset = (S8)*codePtr++ << 8, vertOffset |= *codePtr++;
    }
    *newCodePtrPtr = codePtr;

    size = sizeof(Dvi_Glyph) + RoundUp8(width) * height;
    glyphPtr = (Dvi_Glyph *)ckalloc(size);
    memset(glyphPtr, 0, size);
    glyphPtr->width = width, glyphPtr->height = height;
    glyphPtr->bytesWidth = RoundUp8(width);
    glyphPtr->horizOffset = horizOffset, glyphPtr->vertOffset = vertOffset;
    glyphPtr->renderData = (U8 *)0;
    glyphPtr->shrink = 0;
    glyphPtr->shrinkGlyphPtr = 0;
    return glyphPtr;
}

static Dvi_Glyph *
PkBitmap (codePtr)
    U8 *codePtr;
{
    U8 srcMask, bitMask;
    unsigned int width, height, widthMod8;
    U32 rowBits;
    U32 i;
    Dvi_Glyph *glyphPtr;

    glyphPtr = GlyphHeader(codePtr, &codePtr);
    bitmapPtr = (U8 *)glyphPtr + sizeof(Dvi_Glyph);
    width = glyphPtr->width, height = glyphPtr->height;

    widthMod8 = width % 8;
    if (widthMod8 == 0) {
	memcpy(bitmapPtr, codePtr, (size_t)((width * height) / 8));
    } else {
	srcMask = 0x80;
	for (i = height; i > 0; i--) {
	    rowBits = 0;
	    bitMask = 0x80;
	    while (rowBits < width) {
		*bitmapPtr |= (*codePtr & srcMask) ? bitMask : 0;
		srcMask = (srcMask == 1) ? (++codePtr, srcMask = 0x80)
		    : srcMask >> 1;
		if ((rowBits + 1) % 8 == 0) {
		    bitmapPtr++;
		}
		rowBits++;
		bitMask = (bitMask == 1) ? 0x80 : bitMask >> 1;
	    }
	    bitmapPtr++;
	}
    }
    return glyphPtr;
}

static void
NewNybble(newNybblePtr)
    U8 *newNybblePtr;
{
    firstNybble = 0;
    nybblePtr = newNybblePtr;
}

static U32
GetNybble()
{
    static U8 byte = 0;

    if (firstNybble) {
	firstNybble = 0;
	return byte & 0xf;
    } else {
	byte = *++nybblePtr;
	firstNybble = 1;
	return byte >> 4;
    }
}

static U32
PackedNumber()
{
    U32 i = GetNybble();
    U32 j;

    if (i == 0) {
	do {
	    i++;
	} while ((j = GetNybble()) == 0);
	while (i-- > 0) {
	    j = (j << 4) + GetNybble();
	}
	return j - 15 + ((13 - dynF) << 4) + dynF;
    } else if (i <= dynF) {
	return i;
    } else if (i < 14) {
	return ((i - dynF - 1) << 4) + GetNybble() + dynF + 1;
    } else {
	if (repeatCount != 0) {
	    fprintf(stderr, "Nonzero repeatcount in PK character\n");
	    abort();
	}
	repeatCount = (i == 14) ? PackedNumber() : 1;
	return PackedNumber();
    }
}

static U32
SetBits(position, bits)
    U32 position;
    U32 bits;
{
    unsigned int firstBits = 8 - position;
    static U8 pattern[9] = {
	0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
    };

    if (bits > firstBits) {
	*bitmapPtr++ |= pattern[firstBits] >> position;
	bits -= firstBits;
	while (bits >= 8) {
	    *bitmapPtr++ = 0xff;
	    bits -= 8;
	}
	position = 0;
    }
    *bitmapPtr |= pattern[bits] >> position;
    if ((position += bits) == 8) {
	++bitmapPtr;
    }
    return position % 8;
}

static Dvi_Glyph *
PkRunCount(codePtr)
    U8 *codePtr;
{
    U8 paint = (*codePtr >> 3) & 0x1;
    Dvi_Glyph *glyphPtr;
    unsigned int width, height;
    U8 *bitmapStartPtr;
    unsigned int pinc;
    unsigned int rowBytes, rowBits;
    unsigned long size;
    unsigned int position;

    dynF = *codePtr >> 4;
    glyphPtr = GlyphHeader(codePtr, &codePtr);
    width = glyphPtr->width, height = glyphPtr->height;
    bitmapPtr = bitmapStartPtr = (U8 *)glyphPtr + sizeof(Dvi_Glyph);
    pinc = width % 8 != 0;

    NewNybble(codePtr - 1);
    rowBytes = (width - 1) / 8 + 1;
    size = height * rowBytes;
    repeatCount = rowBits = position = 0;

    while ((unsigned long)(bitmapPtr - bitmapStartPtr) < size) {
	U32 bits = PackedNumber();

	if (rowBits + bits >= width) {
	    bitmapPtr += paint ? (SetBits(position, width-rowBits), pinc)
		: rowBytes - rowBits/8;
	    bits -= (width - rowBits);
	    position = rowBits = 0;
	    if (repeatCount) {
		U8 *sourcePtr = bitmapPtr - rowBytes;
		unsigned int k1;
		for (; repeatCount > 0; repeatCount--) {
		    for (k1 = 0; k1 < rowBytes; k1++) {
			*bitmapPtr++ = sourcePtr[k1];
		    }
		}
	    }
	    while (bits >= width) {
		bitmapPtr += paint ? (SetBits(position, width), pinc)
		    : rowBytes;
		bits -= width;
	    }
	}
	position = paint ? SetBits(position, bits)
	    : (bitmapPtr += (position + bits) / 8, (position + bits) % 8);
	paint = 1 - paint;
	rowBits += bits;
    }
    return glyphPtr;
}

static int
PkClose(dviFont)
    Dvi_Font *dviFont;
{
    PkInfo *infoPtr = (PkInfo *)dviFont->fontData;
    PkCharInfo *charInfoPtr;
    S32 i;

    if (infoPtr == (PkInfo *)0) {
	return TCL_OK;
    }

    charInfoPtr = infoPtr->charInfoPtr;
    for (i = infoPtr->minChar; i <= infoPtr->maxChar; i++) {
	if (charInfoPtr[i - infoPtr->minChar].glyphPtr) {
	    ckfree((char *)charInfoPtr[i - infoPtr->minChar].glyphPtr);
	}
    }
    ckfree((char *)charInfoPtr);
    return TCL_OK;
}
