#ifndef lint
static char sccsid[] = "@(#)fontfiles.c	3.1\t11/16/89";
#endif lint

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <dirent.h>
#include "site.h"
#include "struct.h"
#include "defs.h"


/**********************************************************************/
/*************************  External definitions  *********************/
/**********************************************************************/
extern void   AbortRun(), Fatal(), Progress(), Warning(), GetBytes();
extern int    FindFile();
#ifdef BUILTIN
extern struct BuiltIn *IsBuiltIn();
extern void   LoadBuiltIn(), ReadBuiltIn();
#endif BUILTIN
#ifdef USEPXL
extern void   LoadPixel(), EmitPXL();
extern int    Scanpxl();
#endif USEPXL
#ifdef USEGF
extern void   Loadgfbits(), EmitGF();
extern int    Scangf();
#endif USEGF
#ifndef NO_PK
extern void   LoadPKbits(), EmitPK();
extern int    ScanPK();
#endif NO_PK

extern long   lseek();
extern char  *sprintf(), *malloc(), *getenv();

extern int   mag, num, den, C_fontdensity, hconv, vconv;
extern font_entry *fontptr, *headfont, *prevfont;
extern FILE *dvifp, *outfp;
extern char *C_fontpaths, *G_tfmpaths;
extern char *digit;	/* =  "0123456789ABCDEF";		    */
extern int  G_pagecount, G_permanent, G_temporary;

#ifdef BUDGET
extern int budget;
extern bool G_flushflag;
#endif BUDGET

#ifdef DEBUG
extern bool C_debug;
#endif DEBUG

#ifdef STATS
extern bool C_stats;
extern int  Snbpxl, Sonbpx, Sndc;
#endif STATS

/**********************************************************************/
/**************************  Global variables  ************************/
/**********************************************************************/
FILE *ffp;			/* pointer to the current file of fontdata  */
char *fontdirs[MAXFONTDIRS + 1]; /* font directories as an array of strings */
				/* we allocate an extra for absolute paths  */
int   nfontdirs;		/* length of fontdirs                       */
char *tfmdirs[MAXFONTDIRS];	/* tfm directories as an array of strings   */
int   ntfmdirs;			/* length of tfmdirs                        */
int   nopen = 0;		/* number of open PXL files                 */
font_entry *pfontptr = NULL;	/* previous font_entry pointer              */
struct pixel_list pixel_files[MAXOPEN + 1]; /* list of open PXL file IDs    */

/**********************************************************************/
/**************************  Forward Definitions  *********************/
/**********************************************************************/
float ActualFactor();
void  EmitBits();
void  EmitChar();
void  FindFontFile();
void  LoadAChar();
void  OpenFontFile();
void  ReadFontDef();
void  SetFontDirectories();
void  SkipFontDef();
void  Subdirectory();
void  VerifyDirectories();


/**********************************************************************/
/**************************  ActualFactor  ****************************/
/**********************************************************************/
/* compute the actual size factor from the approximation */
/* a real hack to correct for rounding in some cases--rkf */
float
ActualFactor(unmodsize)
int   unmodsize;	/* actually factor * 1000 */
{
    float realsize;	/* the actual magnification factor */

    realsize = (float) unmodsize / 1000.0;
    if (unmodsize == 1095)
	realsize = 1.095445;	/* stephalf */
    else if (unmodsize == 1315)
	realsize = 1.314534;	/* stepihalf */
    else if (unmodsize == 1577)
	realsize = 1.577441;	/* stepiihalf */
    else if (unmodsize == 1893)
	realsize = 1.892929;	/* stepiiihalf */
    else if (unmodsize == 2074)
	realsize = 2.0736;	/* stepiv */
    else if (unmodsize == 2488)
	realsize = 2.48832;	/* stepv */
    else if (unmodsize == 2986)
	realsize = 2.985984;	/* stepiv */
/* the remaining mag steps are already represented with sufficient accuracy */
    return (realsize);
}


/**********************************************************************/
/****************************  EmitBits  ******************************/
/**********************************************************************/
/* output a character bitmap */
void
EmitBits(c, ce, cmd)	
int   c;
char *cmd;
char_entry *ce;
{
    switch (fontptr->font_type) {
#ifndef NO_PK
    case PKTYPE:
	EmitPK(c, ce, cmd);
	break;
#endif
#ifdef USEGF
    case GFTYPE:
	EmitGF(c, ce, cmd);
	break;
#endif
#ifdef USEPXL
    case PXLTYPE:
	EmitPXL(c, ce, cmd);
	break;
#endif
    default:
	Fatal("EmitBits -- Invalid font type\n");
    }
}


/**********************************************************************/
/****************************  EmitChar  ******************************/
/**********************************************************************/
/* output a character bitmap */
void
EmitChar(c, ce)	
int   c;
char_entry *ce;
{
/* sanity checks */
    if (fontptr == NULL) Fatal("internal - null pointer in EmitChar\n");
    if (fontptr->font_type == PSTYPE) return;
#ifdef BUDGET
    if (BIGCHAR(ce)) return;
#endif BUDGET

/* open font dict before first char */
    if (fontptr->defined < G_permanent) {
#ifdef BUDGET
	if (fontcost(fontptr) >= budget) {
	    G_flushflag = TRUE;
	    return;
	}
	budget -= fontcost(fontptr);
#endif BUDGET
	fontptr->defined = G_permanent;
	EMIT(outfp, "%f /%s @newfont\n",fontptr->ps_scale, fontptr->psname);
    }

    if (fontptr != prevfont && fontptr->dic_pack == FALSE) {
	EMIT(outfp, "%s @sf\n", fontptr->psname);
	prevfont = fontptr;
    }

    EmitBits(c, ce, "@dc");
    ce->where.isloaded = G_permanent;
    fontptr->ncdl++;
#ifdef BUDGET
    budget -= charcost(ce);
#endif BUDGET
}


/**********************************************************************/
/**************************  FindFontFile  ****************************/
/**********************************************************************/
/* Find the font file associated with fptr. */
void
FindFontFile(fptr)
font_entry *fptr;
{
    char  absfontpath[BUFSIZ], *name;
    int   extrapath = 0;

    absfontpath[0] = '\0';
    name = fptr->n;
    if (*fptr->n == '/') {
	while (*name)
	    name++;
	while (*name != '/')
	    name--;
	name++;
	(void) strncat(absfontpath, fptr->n, name - fptr->n);
	fontdirs[nfontdirs] = absfontpath;
	extrapath = 1;
    }
    if (!FindFile(fontdirs, nfontdirs + extrapath, absfontpath, name, fptr))
	Fatal("no font %s.%d", fptr->n, mag);
}


/**********************************************************************/
/*******************************  LoadAChar  **************************/
/**********************************************************************/
void
LoadAChar(c, ptr)
int   c;
register char_entry *ptr;
{
/* sanity checks */
    if (ptr == NULL) Fatal("internal - null pointer in LoadAChar\n");
#ifdef BUILTIN
    if (fontptr->font_type == PSTYPE) {
	LoadBuiltIn(fontptr);
	return;
    }
#endif BUILTIN
    if (ptr->where.isloaded > NOTLOADED && ptr->where.isloaded < G_permanent) {
	ptr->where.isloaded = G_temporary;
	EmitChar (c, ptr);
	return;
    }
    if (ptr->where.address.fileOffset == NONEXISTENT) {
	ptr->where.address.pixptr = NULL;
	ptr->where.isloaded = 999999;
	return;
    }
    OpenFontFile();

    (void) fseek(ffp, ptr->where.address.fileOffset, 0);

    switch (fontptr->font_type) {
#ifndef NO_PK
    case PKTYPE:
	LoadPKbits(ffp, ptr);
	break;
#endif NO_PK
#ifdef USEGF
    case GFTYPE:
	Loadgfbits(ffp, ptr);
	break;
#endif USEGF
#ifdef USEPXL
    case PXLTYPE:
	LoadPixel(ffp, ptr);
	break;
#endif USEPXL
    default:
	Fatal("Unknown font type.\n");
	break;
    }
/* this could be made more efficient? by telling EmitChar the already
 * determined fontfile type */
    EmitChar(c, ptr);
}


/**********************************************************************/
/************************** OpenFontFile  *****************************/
/***********************************************************************
    The original version of this dvi driver reopened the font file  each
    time the font changed, resulting in an enormous number of relatively
    expensive file  openings.   This version  keeps  a cache  of  up  to
    MAXOPEN open files,  so that when  a font change  is made, the  file
    pointer,  ffp,  can usually be updated from the cache.  When 
    the file is not found in the cache, it must be opened. In this case,
    the next empty slot  in the cache  is assigned, or  if the cache  is
    full, the least used font file is closed and its slot reassigned for
    the new file.  Identification of the least used file is based on the
    counts of the number  of times each file  has been "opened" by  this
    routine.  On return, the file pointer is always repositioned to  the
    beginning of the file.

***********************************************************************/
void
OpenFontFile()
{
    register int i, least_used, current;

#ifdef DEBUG
    if (C_debug)
	(void) fprintf(stderr, "Open Font file\n");
#endif
/* sanity checks */
    if (fontptr == NULL)
	Fatal("internal---null font structure pointer in OpenFontFile()\n");

    if (pfontptr == fontptr || fontptr->font_type == PSTYPE)
	return;		/* we need not have been called */

    for (current = 1;
      (current <= nopen) &&
      (pixel_files[current].pixel_file_id != fontptr->font_file_id);
      ++current)
		;	/* try to find file in open list */

    if (current <= nopen) {	/* file already open */
	if ((ffp = pixel_files[current].pixel_file_id) != NO_FILE)
	    (void) fseek(ffp, (long) 0, 0);	/* reposition to start
							 * of file */
    } else {			/* file not in open list */
	if (nopen < MAXOPEN)	/* just add it to list */
	    current = ++nopen;
	else {		/* list full -- find least used file, *//* close it,
			 * and reuse slot for new file */
	    least_used = 1;
	    for (i = 2; i <= MAXOPEN; ++i)
		if (pixel_files[least_used].use_count >
		  pixel_files[i].use_count)
		    least_used = i;
	    if (pixel_files[least_used].pixel_file_id != NO_FILE) {
		FILE *fid;
		font_entry *fp;

		fid = pixel_files[least_used].pixel_file_id;
		fp = headfont;	/* mark file as being closed in the entry */
		while (fp != NULL && fp->font_file_id != fid)
		    fp = fp->next;
		if (fp == NULL)
		    Fatal("Open file %x not found in font entry list.\n", fid);
		else {
		    fp->font_file_id = NULL;
#ifdef STATS
		    if (C_stats)
			Progress("Font file %s closed.\n", fp->name);
#endif STATS
		}
		(void) fclose(fid);
	    }
	    current = least_used;
	}
	if ((ffp = BINARYOPEN(fontptr->name, "r")) == NULL) {
	    Warning("Font file %s could not be opened", fontptr->name);
	    ffp = NO_FILE;
	}
#ifdef STATS
	else {
	    if (C_stats)
		Progress("Font file %s opened.\n", fontptr->name);
	}
#endif STATS
	pixel_files[current].pixel_file_id = ffp;
	pixel_files[current].use_count = 0;
    }
    pfontptr = fontptr;			/* make previous = current font */
    fontptr->font_file_id = ffp;	/* set file identifier */
    pixel_files[current].use_count++;	/* update reference count */
}


/**********************************************************************/
/****************************  ReadFontDef  ***************************/
/**********************************************************************/
/* read a font definition out of the dvi file */
/* then go find the font file (pk, gf, pxl or (for PS) tfm) */
/* read a font definition out of the font file */
void
ReadFontDef(k)
int   k;
{
    int   checksum;
    register int   i;
    register font_entry *tfontptr;	/* temporary font_entry pointer  */
    register char_entry *tcharptr; 	/* temporary char_entry pointer  */
#ifdef BUILTIN
    struct BuiltIn *bi;			/* which builtin font is it?	*/
    bool rbi;
#endif BUILTIN

/* check to see if this font has already been read in */
    tfontptr = headfont;
    while ((tfontptr != NULL) && (tfontptr->k != k)) {
	tfontptr = tfontptr->next;
    }
    if (tfontptr != NULL) {	/* we already read this font */
#ifdef STATS
	if (C_stats)
	    Progress("re-read font file %s\n", tfontptr->name);
#endif STATS
	fontptr = tfontptr;
	return;
    }

/* allocate a new font entry */
    if ((tfontptr = NEW(font_entry)) == NULL)
	Fatal("can't malloc space for font_entry");

/* link this entry into list (at head) */
    tfontptr->next = headfont;
    fontptr = headfont = tfontptr;

/* initialize the font structure */
    tfontptr->font_file_id = NO_FILE;	/* font not yet open */
    tfontptr->ncdl = 0;			/* no characters downloaded */
    tfontptr->defined = NOTLOADED;
    tfontptr->magnification = 0;
    tfontptr->designsize = 0;
    tfontptr->dic_pack = FALSE;
#ifdef STATS
    tfontptr->nbpxl = 0;
    tfontptr->ncts = 0;
#endif
    for (i = FIRSTFNTCHAR; i <= LASTFNTCHAR; i++) {
	tcharptr = &(tfontptr->ch[i]);
	tcharptr->where.address.fileOffset = NONEXISTENT;
	tcharptr->where.isloaded = NOTLOADED;
	tcharptr->tfmw = 0;
	tcharptr->dx = 0;
	tcharptr->width = 0;
	tcharptr->height = 0;
	tcharptr->xOffset = 0;
	tcharptr->yOffset = 0;
	tcharptr->psfont = NONFONT;
	tcharptr->pschar = NONCHAR;
   }

    tfontptr->k = k;
    tfontptr->c = NoSignExtend(dvifp, 4);	/* checksum */
    tfontptr->s = NoSignExtend(dvifp, 4);	/* scale factor */
    tfontptr->d = NoSignExtend(dvifp, 4);	/* design size */
    tfontptr->a = NoSignExtend(dvifp, 1);	/* area length for font name */
    tfontptr->l = NoSignExtend(dvifp, 1);	/* device length */
    GetBytes(dvifp, tfontptr->n, tfontptr->a + tfontptr->l);
    tfontptr->n[tfontptr->a + tfontptr->l] = '\0';
    tfontptr->font_space = tfontptr->s / 6;
    tfontptr->font_mag = (int) ((ActualFactor((int) (((float) tfontptr->s /
		(float) tfontptr->d) * 1000.0 + 0.5)) *
#ifdef USEGLOBALMAG
		ActualFactor(mag) *
#endif USEGLOBALMAG
		(float) C_fontdensity) + 0.5);

#ifdef BUILTIN
    if ((bi = IsBuiltIn(tfontptr->n)) != NULL) {
	rbi = TRUE;
	if (bi->prefix == TRUE) {
	    tfontptr->dic_pack = TRUE;
	    if (bi->defined < G_permanent)
		rbi = FALSE;
	}
	if (rbi == TRUE) {
	    ReadBuiltIn(tfontptr);
	    return;
	}
    }
#endif BUILTIN
    FindFontFile(tfontptr);

/* set the postscript font name; e.g., cmr10.300 */
    (void) sprintf(tfontptr->psname, "%s.%d", tfontptr->n, tfontptr->font_mag);

    if (tfontptr != pfontptr) OpenFontFile();

/* allow missing pxl files */
    if (ffp == NO_FILE)
	return;

    switch (tfontptr->font_type) {
#ifndef NO_PK
    case PKTYPE:
	checksum = ScanPK(tfontptr, tfontptr->font_file_id);
	break;
#endif NO_PK
#ifdef USEGF
    case GFTYPE:
	checksum = Scangf(tfontptr, tfontptr->font_file_id);
	break;
#endif USEGF
#ifdef USEPXL
    case PXLTYPE:
	checksum = Scanpxl(tfontptr, tfontptr->font_file_id);
	break;
#endif USEPXL
    default:
	Fatal("No font types specified.\n");
	AbortRun(1);
    }
    if ((tfontptr->c != 0) && (checksum != 0) && (tfontptr->c != checksum))
	Warning("font = \"%s\",\n-->font checksum = %d,\n-->dvi checksum = %d",
		 tfontptr->name, tfontptr->c, checksum);
}


/**********************************************************************/
/***********************  SetFontDirectories  *************************/
/**********************************************************************/
/* Translate from the colon-separated list in fontpaths to the array of strings
 * in fontdirs and tfmdirs */

void
SetFontDirectories()
{
    char *p, *tmp;

    nfontdirs = 0;
    tmp = p = C_fontpaths;
    while (1) {
	while (*p == ':') {
	    *p++ = 0;
	    VerifyDirectories(tmp);
	}
	if (!*p)
	    break;
	if (nfontdirs == MAXFONTDIRS) {
	    Warning("Too many directories in font search list; ignoring some.\n");
	    break;
	}
	tmp = p;
	while (*p && (*p != ':'))
	    p++;
    }
    VerifyDirectories(tmp);
    if (nfontdirs == 0)
	Fatal("No valid font directories have been specified.\n");

    ntfmdirs = 0;
    p = G_tfmpaths;
    while (1) {
	while (*p == ':')
	    *p++ = 0;
	if (!*p)
	    break;
	if (ntfmdirs == MAXFONTDIRS - 1) {
	    Warning("Too many directories in tfm search list; ignoring some.\n");
	    break;
	}
	tfmdirs[ntfmdirs++] = p;
	while (*p && (*p != ':'))
	    p++;
    }

}


/**********************************************************************/
/****************************  SkipFontDef  ***************************/
/**********************************************************************/
void
SkipFontDef()
{
    int a;
    char n[STRSIZE];

    GetBytes(dvifp, n, 12);
    a = NoSignExtend(dvifp,1) + NoSignExtend(dvifp,1);
    GetBytes(dvifp, n, a);
}


/**********************************************************************/
/**************************  Subdirectory  ****************************/
/**********************************************************************/
void
Subdirectory(path, subpath)
char *path;		/* name of existing directory */
char *subpath;		/* name of subdirectory ? */
{
    DIR  *dirp;
    char  tmp[STRSIZE];

    if (nfontdirs == MAXFONTDIRS)
	return;		/* no more room */
    (void) strcpy(tmp, path);
    (void) strcat(tmp, "/");
    (void) strcat(tmp, subpath);	/* store full path name */
    if ((dirp = opendir(tmp)) == NULL)
	return;		/* doesn't exist */
    (void) closedir(dirp);
    fontdirs[nfontdirs] = malloc(STRSIZE);	/* add to list */
    (void) strcpy(fontdirs[nfontdirs++], tmp);
}


/**********************************************************************/
/************************  VerifyDirectories  *************************/
/**********************************************************************/
void
VerifyDirectories(p)
char *p;		/* name of directory */
{
    DIR  *dirp;		/* pointer to directory */

    if ((dirp = opendir(p)) == NULL)
	Warning("Font directory %s does not exist\n", p);
    else {
	(void) closedir(dirp);
	fontdirs[nfontdirs] = malloc(STRSIZE);
	(void) strcpy(fontdirs[nfontdirs++], p);	/* add to list */

    /* look for obvious subdirectories */
#ifndef NO_PK
	Subdirectory(p, "pk");
	Subdirectory(p, "pk300");
#endif
#ifdef USEGF
	Subdirectory(p, "gf");
	Subdirectory(p, "gf300");
#endif
#ifdef USEPXL
	Subdirectory(p, "pxl");
#endif
    }
    return;
}
