/* $Header: fc.c,v 1.3 87/06/22 12:22:27 toddb Exp $ */

#include <stdio.h>
#include <strings.h>
/*
#include <sys/file.h> 
#include <errno.h> 
*/
/* #include <malloc.h> */	extern char *malloc(), *realloc();

#include "misc.h"
#include "X.h"
#include "Xproto.h"
#include "fontstruct.h"
#include "font.h"

#include "fc.h"	/* used by converters only */

/*
 * DESCRIPTION
 *	pure filter; filename argument only
 *
 * DISCLAIMER
 *	not much thought has been given to error recovery.
 */
#define INDICES 256
#define MAXENCODING 0xFFFF

extern char *gets(), *index();

static char *myname;	/* initialized from argv[0] */
static char *currentfont;

int linenum = 0;	/* for error messages */
int or←glyphPad = 0;	/* override glyphPading?? */
int or←bitorder = 0;	/* override bitorder?? */


#ifdef vax

int glyphPad = 1;		/* default padding for glyphs */
int bitorder = LSBFirst;	/* default bitmap bit order */

#else
# ifdef sun

int glyphPad = 4;		/* default padding for glyphs */
int bitorder = MSBFirst;	/* default bitmap bit order */

# else
#  ifdef apollo

int glyphPad = 1;		/* default padding for glyphs */
int bitorder = MSBFirst;	/* default bitmap bit order */

#  else
#   ifdef ibm032

int glyphPad = 1;		/* default padding for glyphs */
int bitorder = MSBFirst;	/* default bitmap bit order */

#   else
#    ifdef princops

int glyphPad = 4;
int bitorder = MSBFirst;

#    else
int glyphPad = 1;		/* default padding for glyphs */
int bitorder = MSBFirst;	/* default bitmap bit order */
#   define UNSPECIFIED

#    endif
#   endif
#  endif
# endif
#endif

/*
 * read the next line and keep a count for error messages
 */
char *
getline(s)
    char *s;
{
    s = gets(s);
    linenum++;
    while (s) {
	int len = strlen(s);
	if (len && s[len-1] == '\015')
	    s[--len] = '\0';
	if ((len==0) || prefix(s, "COMMENT")) {
	    s = gets(s);
	    linenum++;
	} else break;
    }
    return(s);
}

/*
 * malloc and copy a string value. Handle quoted strings.
 */
char *
remember(s)
char *s;
{
    char *p, *pp;

    /* strip leading white space */
    while (*s && (*s == ' ' || *s == '\t'))
	s++;
    if (*s == 0)
    	return "";
    if (*s != '"') {
	pp = s;
	/* no white space in value */
	for (pp=s; *pp; pp++)
	    if (*pp == ' ' || *pp == '\t' || *pp == '\015' || *pp == '\n') {
	        *pp = 0;
		break;
	    }
	p = malloc(strlen(s)+1);
	strcpy(p, s);
	return p;
    }
    /* quoted string: strip outer quotes and undouble inner quotes */
    s++;
    pp = p = malloc(strlen(s)+1);
    while (*s) {
	if (*s == '"') {
	    if (*(s+1) != '"') {
	    	*p++ = 0;
		return pp;
	    } else {
		s++;
	    }
	}
	*p++ = *s++;
    }
    *p++ = 0; /* just in case; space for it allocated above */
    return pp;
}

/*
 *	Invert bit order within each BYTE of an array.
 *	"In" and "out" may point to the same array.
 */
void
bitorderinvert( in, out, nbytes)
    register unsigned char	*in;
    register unsigned char	*out;
    register int	nbytes;
{
    static int BOTableInitialized = 0;
    static unsigned char BOTable[256];


    if ( !BOTableInitialized)
    {
	int	btabi;
	int	biti;

	for ( btabi=0; btabi<256; btabi++)
	    for ( biti=0; biti<8; biti++)
		if (btabi & 1<<biti)
		    BOTable[btabi] |= (unsigned)0x80 >> biti;
	BOTableInitialized++;
    }
    while ( nbytes--)
	*out++ = BOTable[ *in++];

}

/*
 * return TRUE if str is a prefix of buf
 */
prefix(buf, str)
    char *buf, *str;
{
    return strncmp(buf, str, strlen(str))? FALSE : TRUE;
}

/*
 * return TRUE if strings are equal
 */
streq(a, b)
    char *a, *b;
{
    return strcmp(a, b)? FALSE : TRUE;
}

/*
 * make a byte from the first two hex characters in s
 */
unsigned char
hexbyte(s)
    char *s;
{
    unsigned char b = 0;
    register char c;
    int i;

    for (i=2; i; i--) {
	c = *s++;
	if ((c >= '0') && (c <= '9'))
	    b = (b<<4) + (c - '0');
	else if ((c >= 'A') && (c <= 'F'))
	    b = (b<<4) + 10 + (c - 'A');
	else if ((c >= 'a') && (c <= 'f'))
	    b = (b<<4) + 10 + (c - 'a');
	else
	    return 0; /* bad data */
    } 
    return b;
}

/*
 * fatal error. never returns.
 */
fatal(msg, p1, p2, p3, p4)
    char *msg;
{
    fprintf(stderr, "%s: %s:", myname, currentfont);
    fprintf(stderr, msg, p1, p2, p3, p4);
    if (linenum)
	fprintf(stderr, " at line %d\n", linenum);
    else
	fprintf(stderr, "\n");
    exit(1);
}

/*
 * these properties will be generated if not already present.
 */
#define NULLPROP (FontPropPtr)0;

FontPropPtr pointSizeProp = NULLPROP;
FontPropPtr familyProp = NULLPROP;
FontPropPtr resolutionProp = NULLPROP;
FontPropPtr xHeightProp = NULLPROP;
FontPropPtr weightProp = NULLPROP;
FontPropPtr quadWidthProp = NULLPROP;
#define GENPROPS 6

BOOL haveFontAscent = FALSE;
BOOL haveFontDescent = FALSE;

/*
 * check for known property values
 */

int
specialproperty(pfp, pfi)
    FontPropPtr pfp;
    FontInfoPtr pfi;
{
    if (streq(pfp->name, "FONT←ASCENT") && !pfp->indirect)
    {
	pfi->fontAscent = pfp->value;
	haveFontAscent = TRUE;
	return 0;
    }
    else if (streq(pfp->name, "FONT←DESCENT") && !pfp->indirect)
    {
	pfi->fontDescent = pfp->value;
	haveFontDescent = TRUE;
	return 0;
    }
    else if (streq(pfp->name, "DEFAULT←CHAR") && !pfp->indirect)
    {
	pfi->chDefault = pfp->value;
	return 0;
    }
    else if (streq(pfp->name , "POINT←SIZE"))
	pointSizeProp = pfp;
    else if (streq(pfp->name , "FAMILY←NAME"))
	familyProp = pfp;
    else if (streq(pfp->name , "RESOLUTION"))
	resolutionProp = pfp;
    else if (streq(pfp->name , "X←HEIGHT"))
	xHeightProp = pfp;
    else if (streq(pfp->name , "WEIGHT"))
	weightProp = pfp;
    else if (streq(pfp->name , "QUAD←WIDTH"))
	quadWidthProp = pfp;
    return 1;
}

computeweight(font)
    TempFont *font;
{
    int i;
    int width = 0, area, bits = 0;
    register b;
    register unsigned char *p;

    for (i=0; i<n1dChars(font->pFI); i++)
	width += font->pCI[i].metrics.characterWidth;
    area = width*(font->pFI->fontAscent+font->pFI->fontDescent);
    for (i=0,p=font->pGlyphs; i<font->pFI->maxbounds.byteOffset; i++,p++)
    	for (b=(*p); b; b >>= 1)
	    bits += b & 1;
    return (int)((bits*1000.0)/area);
}

main(argc, argv)
    int		argc;
    char *	argv[];
{
    TempFont	font;
    FontInfoRec	fi;
    CharInfoPtr	cinfos[INDICES];	/* rows waiting to be allocated */
    int		bytesGlAlloced = 1024;	/* amount now allocated for glyphs
					   (bytes) */
    unsigned char *pGl = (unsigned char *)malloc( bytesGlAlloced);
    int		bytesGlUsed = 0;
    int		nGl = 0;
    int		nchars;
    float	pointSize;
    int		xRes, yRes;
    char	linebuf[BUFSIZ];
    char	namebuf[100];
    char	family[100];
    char	*bdffile = NULL;
    unsigned int attributes;
    int		digitWidths = 0, digitCount = 0, ex = 0;
    int		char←row, char←col;
    int		i;
    CharInfoRec	emptyCharInfo;

    myname = argv[0];
    argc--, argv++;
    while (argc--) {
	if (argv[0][0] == '-') {
	    switch (argv[0][1]) {
	    case 'p':	/* Pad Glyphs to Word boundaries */
		switch (argv[0][2]) {
		default:
		    goto usage;
		case '1':
		case '2':
		case '4':
		case '8':
		    glyphPad = argv[0][2] - '0';
		    break;
		}
		or←glyphPad = 1;
		break;
	    case 'm':
		or←bitorder = 1;
		bitorder = MSBFirst;
		break;

	    case 'l':
		or←bitorder = 1;
		bitorder = LSBFirst;
		break;

	    default:
		fprintf(stderr, "bad flag -%c\n", argv[0][1]);
		break;
	    }
	} else {
	    if (bdffile)
	usage:
		fatal("usage: %s [-p#] [bdf file]", myname);
	    bdffile = argv[0];
	    if (freopen( bdffile, "r", stdin) == NULL)
		fatal("could not open  file %s\n", bdffile);
	}
	argv++;
    }
    emptyCharInfo.metrics.leftSideBearing = 0;
    emptyCharInfo.metrics.rightSideBearing = 0;
    emptyCharInfo.metrics.ascent = 0;
    emptyCharInfo.metrics.descent = 0;
    emptyCharInfo.metrics.characterWidth = 0;
    emptyCharInfo.byteOffset = 0;
    emptyCharInfo.exists = FALSE;
    emptyCharInfo.metrics.attributes = 0;

    for (i = 0; i < INDICES; i++)
	cinfos[i] = (CharInfoPtr)NULL;

    font.pFI = &fi;
    fi.firstRow = INDICES;
    fi.lastRow = 0;
    fi.chFirst = INDICES;
    fi.chLast = 0;
    fi.pixDepth = 1;
    fi.glyphSets = 1;
    fi.chDefault = 0;	/* may be overridden by a property */

    getline(linebuf);

    if ((sscanf(linebuf, "STARTFONT %s", namebuf) != 1) ||
	!streq(namebuf, "2.1"))
	fatal("bad 'STARTFONT'");
    getline(linebuf);

    if (sscanf(linebuf, "FONT %s", family) != 1)
	fatal("bad 'FONT'");
    getline(linebuf);

    if (!prefix(linebuf, "SIZE"))
	fatal("missing 'SIZE'");
    if ((sscanf(linebuf, "SIZE %f%d%d", &pointSize, &xRes, &yRes) != 3))
	fatal("bad 'SIZE'");
    if (xRes != yRes)
        fatal("x and y resolution must be equal");
    getline(linebuf);

    if (!prefix(linebuf, "FONTBOUNDINGBOX"))
	fatal("missing 'FONTBOUNDINGBOX'");
    getline(linebuf);

    if (prefix(linebuf, "STARTPROPERTIES")) {
	int nprops;
	FontPropPtr pfp;

	sscanf(linebuf, "%*s%d", &nprops);
	fi.nProps = nprops;
	pfp = (FontPropPtr)malloc((nprops+GENPROPS) * sizeof(FontPropRec));
	font.pFP = pfp;
	getline(linebuf);
	while((nprops-- > 0) && !prefix(linebuf, "ENDPROPERTIES")) {
	    if (sscanf(linebuf, "%s%d", namebuf, &pfp->value) == 2) {
		/* integer value */
		pfp->indirect = FALSE;
	    } else {
		/* value is (possibly quoted) string */
		pfp->indirect = TRUE;
		pfp->value = (INT32)remember(linebuf+strlen(namebuf)+1);
	    }
	    pfp->name = (CARD32)remember(namebuf);
	    if (specialproperty(pfp, &fi))
	        pfp++;
	    else
		fi.nProps--;
	    getline(linebuf);
	}
	if (!prefix(linebuf, "ENDPROPERTIES"))
	    fatal("missing 'ENDPROPERTIES'");
	if (!haveFontAscent || !haveFontDescent)
	    fatal("must have 'FONT←ASCENT' and 'FONT←DESCENT' properties");
	if (nprops != -1)
	    fatal("%d too few properties", nprops+1);
	if (!familyProp) {
	    fi.nProps++;
	    pfp->name = (CARD32)("FAMILY←NAME");
	    pfp->value = (INT32)family;
	    pfp->indirect = TRUE;
	    familyProp = pfp++;
	}
	if (!pointSizeProp) {
	    fi.nProps++;
	    pfp->name = (CARD32)("POINT←SIZE");
	    pfp->value = (INT32)(pointSize*10.0);
	    pfp->indirect = FALSE;
	    pointSizeProp = pfp++;
	}
	if (!weightProp) {
	    fi.nProps++;
	    pfp->name = (CARD32)("WEIGHT");
	    pfp->value = -1;	/* computed later */
	    pfp->indirect = FALSE;
	    weightProp = pfp++;
	}
	if (!resolutionProp) {
	    fi.nProps++;
	    pfp->name = (CARD32)("RESOLUTION");
	    pfp->value = (INT32)((xRes*100.0)/72.27);
	    pfp->indirect = FALSE;
	    resolutionProp = pfp++;
	}
	if (!xHeightProp) {
	    fi.nProps++;
	    pfp->name = (CARD32)("X←HEIGHT");
	    pfp->value = -1;	/* computed later */
	    pfp->indirect = FALSE;
	    xHeightProp = pfp++;
	}
	if (!quadWidthProp) {
	    fi.nProps++;
	    pfp->name = (CARD32)("QUAD←WIDTH");
	    pfp->value = -1;	/* computed later */
	    pfp->indirect = FALSE;
	    quadWidthProp = pfp++;
	}
    } else { /* no properties */
	fatal("missing 'PROPERTIES'");
    }
    getline(linebuf);

    if (!prefix(linebuf, "CHARS"))
        fatal("missing 'CHARS'");
    sscanf(linebuf, "%*s%d", &nchars);
    getline(linebuf);

    while ((nchars-- > 0) && prefix(linebuf, "STARTCHAR"))  {
        int	t;
	int	ix;	/* counts bytes in a glyph */
	int	wx;	/* x component of width */
	int	bw;	/* bounding-box width */
	int	bh;	/* bounding-box height */
	int	bl;	/* bounding-box left */
	int	bb;	/* bounding-box bottom */
	int	enc, enc2;	/* encoding */
	char	*p;	/* temp pointer into linebuf */
	int	bytesperrow, row;
	char	charName[100];

	if (sscanf(linebuf, "STARTCHAR %s", charName) != 1)
	    fatal("bad character name");

	getline( linebuf);
	if ((t=sscanf(linebuf, "ENCODING %d %d", &enc, &enc2)) < 1)
	    fatal("bad 'ENCODING'");
	if (t == 2 && enc == -1)
	    enc = enc2;
	if (enc == -1) {
	    fprintf(stderr,
	        "character '%s'with encoding = -1 ignored at line %d\n",
		charName, linenum);
	    do {
	    	char *s = getline(linebuf);
		if (!s)
		    fatal("Unexpected EOF");
	    } while (!prefix(linebuf, "ENDCHAR"));
	    getline(linebuf);
	    continue;
	}
	if (enc > MAXENCODING)
	    fatal("character '%s' has encoding(=%d) too large", charName, enc);
	char←row = (enc >> 8) & 0xFF;
	char←col = enc & 0xFF;
	fi.firstRow = MIN(fi.firstRow, char←row);
	fi.lastRow = MAX(fi.lastRow, char←row);
	fi.chFirst = MIN(fi.chFirst, char←col);
	fi.chLast = MAX(fi.chLast, char←col);
	if (!cinfos[char←row])
	{
	    cinfos[char←row] =
		(CharInfoPtr)malloc(sizeof(CharInfoRec)*INDICES);
	    bzero(cinfos[char←row], sizeof(CharInfoRec)*INDICES);
	}

	getline( linebuf);
	if (!prefix(linebuf, "SWIDTH"))
	    fatal("bad 'SWIDTH'");

	getline( linebuf);
	sscanf( linebuf, "DWIDTH %d %*d", &wx);

	getline( linebuf);
	sscanf( linebuf, "BBX %d %d %d %d", &bw, &bh, &bl, &bb);

	getline( linebuf);
	if (prefix(linebuf, "ATTRIBUTES"))
	{
	    for (p = linebuf + strlen("ATTRIBUTES ");
		(*p == ' ') || (*p == '\t');
		p ++)
		/* empty for loop */ ;
	    attributes = hexbyte(p)<< 8 + hexbyte(p+2);
	    getline( linebuf);	/* set up for BITMAP which follows */
	}
	else
	    attributes = 0;
	if (!prefix(linebuf, "BITMAP"))
	    fatal("missing 'BITMAP'");

	/* collect data for generated properties */
	if ((strlen(charName) == 1)){
	    if ((charName[0] >='0') && (charName[0] <= '9')) {
		digitWidths += wx;
		digitCount++;
	    } else if (charName[0] == 'x') {
	        ex = (bh+bb)<=0? bh : bh+bb ;
	    }
	}

	cinfos[char←row][char←col].metrics.leftSideBearing = bl;
	cinfos[char←row][char←col].metrics.rightSideBearing = bl+bw;
	cinfos[char←row][char←col].metrics.ascent = bh+bb;
	cinfos[char←row][char←col].metrics.descent = -bb;
	cinfos[char←row][char←col].metrics.characterWidth = wx;
	cinfos[char←row][char←col].byteOffset = bytesGlUsed;
	cinfos[char←row][char←col].exists = FALSE;  /* overwritten later */
	cinfos[char←row][char←col].metrics.attributes = attributes;

	bytesperrow = GLWIDTHBYTESPADDED(bw,glyphPad);
	for (row=0; row < bh; row++) {
	    getline(linebuf);
	    p = linebuf;
	    for ( ix=0; ix < bytesperrow; ix++)
	    {
	        if ( bytesGlUsed >= bytesGlAlloced)
	    	pGl = (unsigned char *)realloc( pGl, (bytesGlAlloced *= 2));
	        pGl[bytesGlUsed] = hexbyte(p);
	        p += 2;
	        bytesGlUsed++;
	    }
 	    /*
 	     *  Now pad the glyph row our pad boundary.
 	     */
	    bytesGlUsed = GLWIDTHBYTESPADDED(bytesGlUsed<<3,glyphPad);
	}
	getline( linebuf);
	if (!prefix(linebuf, "ENDCHAR"))
            fatal("missing 'ENDCHAR'");
	nGl++;
	getline( linebuf);		/* get STARTCHAR or ENDFONT */
    }

    if (!prefix(linebuf, "ENDFONT"))
        fatal("missing 'ENDFONT'");
    if (nchars != -1)
        fatal("%d too few characters", nchars+1);
    if (nGl == 0)
        fatal("No characters with valid encodings");

    fi.maxbounds.byteOffset = bytesGlUsed;
    font.pGlyphs = pGl;

    font.pCI = (CharInfoPtr)malloc(sizeof(CharInfoRec)*n2dChars(font.pFI));
    i = 0;
    for (char←row = fi.firstRow; char←row <= fi.lastRow; char←row++)
    {
	if (!cinfos[char←row])
	    for (char←col = fi.chFirst; char←col <= fi.chLast; char←col++)
		{
		font.pCI[i] = emptyCharInfo;
		i++;
		}
	else
	    for (char←col = fi.chFirst; char←col <= fi.chLast; char←col++)
		{
		font.pCI[i] = cinfos[char←row][char←col];
		i++;
		}
    }
    computeNaccelerators(&font);

    /* generate properties */
    if (xHeightProp && (xHeightProp->value == -1))
        xHeightProp->value = ex? ex : fi.minbounds.metrics.ascent;
    if (quadWidthProp && (quadWidthProp->value == -1))
        quadWidthProp->value = digitCount?
	    (INT32)((float)digitWidths/(float)digitCount) :
	    (fi.minbounds.metrics.characterWidth+fi.maxbounds.metrics.characterWidth)/2;
    if (weightProp && (weightProp->value == -1))
        weightProp->value = computeweight(&font);

    if (bitorder == LSBFirst)
	bitorderinvert( pGl, pGl, bytesGlUsed);
#ifdef UNSPECIFIED
    if (!or←glyphPad || !or←bitorder) {
	fprintf(stderr, "%s: ", currentfont);
	if (!or←glyphPad && !or←bitorder)
	    fprintf(stderr, "bit order/pad unspecified:\n\t");
	else if (!or←glyphPad)
	    fprintf(stderr, "pad unspecified:\n\t");
	else
	    fprintf(stderr, "bit order unspecified:\n\t");
	fprintf(stderr, "using ");

	if (!or←bitorder)
	    fprintf(stderr, "order=%s",
		bitorder == LSBFirst ? "LSBFirst" : "MSBFirst");
	if (!or←glyphPad && !or←bitorder)
	    fprintf(stderr, ", ");
	if (!or←glyphPad)
	    fprintf(stderr, "pad=%d", glyphPad);
	fprintf(stderr, "\n");
    }
#endif

    WriteNFont( stdout, &font);
    exit(0);
}