/* kyofonts.c: font module for dvi printing on Kyocera laser printers
 * Adapted from the qmsfonts module from Massachusetts Institute of Technology
 */

#include <stdio.h>
#include "util.h"
#include "fonts.h"
#include "findfile.h"
#include "kyo.h"
#include "preload.h"
#include <sys/stat.h>

#define MAXDVIFONTS     256    /* Max number of fonts allowed in a DVI file*/
#define MAXFNTDIR        16    /* Max number of dirs to look for fonts */

unsigned char *malloc();
char *strcat();
char *strcpy();
char *strncpy();
char *strncmp();
char *strcmp();

long numerator,denominator,half_denominator,jobmag;

long     freewords = KYOMAXWORDS;    /* free space on printer */
FILE     *kyo;                       /* output device */
char *filter_name;                   /* the name of the calling prog */
char *fntdirvec[MAXFNTDIR];
int fntdirveclen;

int nkyofonts;
struct kyofont *curfnt;         /* ptr to current and last font */
long timestamp;                 /* Timestamp for stamping kyo fonts */
int nloaded;                    /* Number of fonts already loaded */
char *loadfile = PRLOAD ;
char fontnums[MAXKYOFONTS];

#define PR_USED   0x0001
#define PR_LOADED 0x0002

struct preload {
  struct preload_info *pr_font;
  short               pr_info;
} loaded[MAXKYOFONTS];

/* The dvifont struct provides a (possible) many-to-one mapping
 * because several DVI fonts can map to the same Kyocera font during font
 * substitution.
 */
struct dvifont {
  unsigned long    df_num;         /* DVI font number */
  struct kyofont   *df_kyofont;    /* Font which contains */
} dvifonts[MAXDVIFONTS];
int ndvifonts;

long max(a,b)
     long a,b;
{
  return((a>b)? a:b);
}

long min(a,b)
     long a,b;
{
  return((a<b)? a:b);
}

/* called at the beginning of the driver program */
f_init(printer,pgmnam,dirvec,dirveclen,num,den,mag,options)
    FILE *printer;
    char *pgmnam,*dirvec[];
    int dirveclen;
    long num,den,mag;
    unsigned long options;
{
  int i, n, loadf;
  struct stat s;
  struct preload_info *fnt;

  numerator = num;
  denominator = den;
  half_denominator = denominator / 2;
  jobmag = mag;
  fntdirveclen = min(MAXFNTDIR,dirveclen);
  for (i = 0; i < fntdirveclen; i++) fntdirvec[i] = dirvec[i];
  ndvifonts = 0;
  nkyofonts = 0;
  timestamp = 0;
  kyo = printer;
  filter_name = pgmnam;

  /* Try to read the file with information about the
   * fonts loaded in the Kyocera at this moment
   * The preload file should exist and have a length
   * that is a multiple of the length of a preload-record
   * This to protect against corrupted preload information
   * When no preload information is present or when it is corrupt
   * dump an explicit DAF (delete all fonts) command
   */
  if (((loadf = open(loadfile, 0)) >= 0) &&
      (stat(loadfile, &s) == 0) &&
      (s.st_size > 0) &&
      ((s.st_size % sizeof(struct preload_info)) == 0)) {
      n = s.st_size / sizeof(struct preload_info);
      debug("%d fonts were preloaded\n", n);
      for (i=0; i<n; i++) {
	  if (!(loaded[nloaded].pr_font = fnt =
		 (struct preload_info *) malloc(sizeof(struct preload_info))))
	      croak("malloc %d",sizeof(struct preload_info));
	  loaded[nloaded++].pr_info = PR_LOADED;
	  if (read(loadf, fnt, sizeof(struct preload_info)) !=
			       sizeof(struct preload_info)) {
	      nloaded--;
	      debug("Could read the info of only %d preloaded fonts\n",
		    nloaded);
	      break;
	  }
	  freewords -= fnt->pr_words_used;
	  fontnums[fnt->pr_kyonumber-KF_NUMBASE] = KF_USED;
	  debug("%s\n", fnt->pr_name);
      }
      close(loadf);
  } else fprintf(kyo, "!R! DAF;EXIT;");
  debug("Free font memory %ld words\n",freewords);
  /* Clear the preload file before going on
   * So when the driver crashes, it will not leave the next jobs
   * with incorrect preload information
   */
  close(creat(loadfile, 0644));
}

/* Called at the end of the driver program. */
f_term()
{
  int i, j, loadf;
  struct kyofont *fnt;
  struct kyochar *c;
  struct preload_info font;
  struct preload *pfnt;

  if ((loadf = creat(loadfile, 0644)) < 0) {
      debug("Cannot create %s\n", loadfile);
      return;
  }

  /* Just copy back the information for fonts not used by this job */
  for (i=0; i<nloaded; i++) {
    pfnt = &loaded[i];
    if (!(pfnt->pr_info & PR_USED) &&
	 (pfnt->pr_info & PR_LOADED))
      write(loadf, pfnt->pr_font, sizeof(struct preload_info));
  }

  /* Write back the information about the fonts that are used by this job */
  for (i=0; i<nkyofonts; i++) {
      fnt = kyofonts[i];
      if (fnt->kf_info & KF_LOADED) {
	  font.pr_kyonumber = fnt->kf_kyonumber;
	  strcpy(font.pr_name, fnt->kf_name);
	  font.pr_mag = fnt->kf_mag;
	  font.pr_words_used = fnt->kf_words_used;
	  font.pr_tfm_checksum = fnt->kf_tfm_checksum;
	  for (j=0; j<4; j++) font.pr_charvec[j] = 0;
	  for (j=0; j<MAXCHARS; j++) {
	      c = &(fnt->kf_char[j]);
	      if (c->kc_info & KC_LOADED)
		  font.pr_charvec[j/32] |= (1 << (j % 32));
	  }
	  write(loadf, &font, sizeof(struct preload_info));
      }
  }
  debug( "Font memory left: %ld words\n", freewords);
}

/* This routine provides for an independent numbering scheme
 * for the fonts loaded into the Kyocera
 * It returns the first free number
 * Fonts are numbered starting at KF_NUMBASE
 */
short next_kyonumber()
{
  int i;

  for (i=0; i<MAXKYOFONTS; i++)
    if (!fontnums[i]) {
      fontnums[i] = KF_USED;
      return(i+KF_NUMBASE);
    }
}

/* Look if font "name" is preloaded
 * If it is return a pointer to the preload information
 * Ohterwise return 0
 */
struct preload *
preloaded(name, tfmchecksum, mag)
char *name;
long tfmchecksum;
{
  int i;
  struct preload *pnt;
  register struct preload_info *fnt;

  for (i=0; i<nloaded; i++) {
      pnt = &loaded[i];
      fnt = pnt->pr_font;
      /* The name, magnification and tfm-checksum should match */
      if ((pnt->pr_info & PR_LOADED) &&
	  (strcmp(fnt->pr_name, name) == 0) &&
	  (fnt->pr_mag == mag) &&
	  (fnt->pr_tfm_checksum == tfmchecksum)) {
	return(pnt);
      }
  }
  return((struct preload *) 0);
}

/* Try to locate the font "name" with the specified attributes
 * in "area"
 */
struct kyofont *
find_kyofont(area,name,texmag,s,tfmchecksum)
     char *area,*name;
     long texmag,s,tfmchecksum;
{
  char fname[MAXNAMLEN],nname[128];
  int i,nmag;
  int mag = (texmag * 3 + 1) / 2;
  struct kyofont *fnt;
  struct preload *pfnt;
  struct preload_info *pnt;

  /* try to find a font file */
  if (!findfile(fntdirvec,fntdirveclen,area,name,mag,fname,nname,&nmag))
    croak("no font %s.%d",name,texmag);

  /* make a new kyocera font */
  if (nkyofonts >= MAXKYOFONTS) croak("too many kyo fonts");

  if (!(kyofonts[nkyofonts++] = fnt =
	(struct kyofont *) malloc(sizeof(struct kyofont))))
      croak("malloc %d",sizeof(struct kyofont));
  fnt->kf_mag = nmag;
  fnt->kf_timestamp = 0;
  fnt->kf_s = s;
  fnt->kf_tfm_checksum = tfmchecksum;
  strcpy(fnt->kf_name,nname);
  strcpy(fnt->kf_filename,fname);
  for (i=0; i<MAXCHARS; i++)
      fnt->kf_char[i].kc_info = 0;

  if (pfnt = preloaded(nname, tfmchecksum,nmag)) {
      pnt = pfnt->pr_font;
      fnt->kf_kyonumber = pnt->pr_kyonumber;
      fnt->kf_words_needed = 0;
      fnt->kf_words_used = pnt->pr_words_used;
      for (i=0; i<MAXCHARS; i++)
	  if (pnt->pr_charvec[i/32] & ( 1 << (i % 32)))
	      fnt->kf_char[i].kc_info = KC_LOADED;
      fnt->kf_info = KF_FILE | KF_LOADED;
      pfnt->pr_info |= PR_USED;
  } else {
      fnt->kf_kyonumber = next_kyonumber();
      fnt->kf_words_needed = KF_MIN;
      fnt->kf_words_used = 0;
      fnt->kf_info = KF_FILE;
  }
  return(fnt);
}

/* Read the font information from the pxl file
 * and remember that it is done
 */
getfontfromfile(fnt)
     struct kyofont *fnt;
{
  char fname[MAXNAMLEN];
  FILE *f;
  int i;

  readpxlfile(fnt,fnt->kf_filename);
  fnt->kf_info &= ~KF_FILE;
}

/* Calculate the number of words needed by symbol "c"
 * for its pxl raster
 */
int char_words_needed(c)
     struct kyochar *c;
{
    return(((c->kc_width + 15) / 16) * c->kc_height);
}

/* Define a font.
 * Can be called more than once for the same font.
 */
f_define_font(num,options,area,name,texmag,s,tfmchecksum)
char *area,*name;
unsigned long num,options,texmag,s,tfmchecksum;
{
  int i;

  /* check to see if font is already defined */
  for(i = 0; i < ndvifonts ; i++) if (dvifonts[i].df_num == num) return;

  /* Does this make too many fonts defined? */
  if (ndvifonts >= MAXDVIFONTS) croak("too many dvi fonts");
  dvifonts[ndvifonts].df_num = num;
  dvifonts[ndvifonts++].df_kyofont = find_kyofont(area,name,texmag
						 ,s,tfmchecksum);
}

/* Try to find DVI font with number "fontnum"
 * in the dvi-fonttable
 * Timestamp it to enable LRU font removal
 */
struct kyofont *
find_dvi_font(fontnum)
     unsigned long fontnum;
{
  register int i;

  /* scan through the dvi font list. */
  for(i = 0; i < ndvifonts ; i++)
    if (dvifonts[i].df_num == fontnum) {
      dvifonts[i].df_kyofont->kf_timestamp = timestamp++;
      return(dvifonts[i].df_kyofont);
    }
  croak("no dvi font %d defined",fontnum);
}

/* Select a font as the current font */
f_use_font(fontnum,font_space)
unsigned long fontnum;
long *font_space;
{
  curfnt = find_dvi_font(fontnum);
  /* The font should be loaded already
   * because loading fonts while printing a page
   * could cause the Kyocera to emit the sheet
   * that is printed on at this moment
   */
  if (!(curfnt->kf_info & KF_LOADED))
    croak("cannot load font %d while printing page", fontnum);
  /* Start using it */
  fprintf(kyo,"!R! FONT %1d;EXIT;",curfnt->kf_kyonumber);
  *font_space = curfnt->kf_s / 6;
}

/* Put symbol "ch" to the printer
 * "xsize" is the horizontal width of the character in pixels
 */
f_setc(ch, xsize)
unsigned long ch;
int xsize;
{
  register struct kyochar *c = &(curfnt->kf_char[ch]);

  /* When the symbol is to high it must be sent in raster format
   * and canoot be loaded into the Kyocera
   * Symbols higher than MAXKYOHEIGHT pixels cannot be loaded
   */
  if (c->kc_info & KC_RASTER)
    dev_raster(c);
  else
    dev_setc(ch, xsize);
}

/* Hints as to what fonts are to be used.
 * This is called once per page to give fonts module instrucions as what fonts
 * and characters in those fonts have to be loaded for this page.
 * "fontvec" contains a list of fonts used for this page
 * "veclen" is the number of fonts used on this page
 * "charvec" contains the per font information about the symbols needed
 */
f_newpage(fontvec,charvec,veclen)
     unsigned long fontvec[];
     unsigned long charvec[][4];
     int veclen;
{
  struct kyochar *c;
  register int i,j;
  struct kyofont *f;

  for (i = 0; i < veclen; i++) {
    /* Lookup font in the dvi-fonttable
     * and read the pxlfile if needed
     */
    f = find_dvi_font(fontvec[i]);
    if (f->kf_info & KF_FILE) getfontfromfile(f);
    /* If not not loaded initialize space requirements */
    if (!(f->kf_info & KF_LOADED)) {
      f->kf_words_used = 0;
      f->kf_words_needed = KF_MIN;

    }
    /* Determine which symbols should be loaded from this font */
    for (j = 0; j < MAXCHARS; j++)
      if (charvec[i][j/32] & (1 << (j % 32))) {
	c = &(f->kf_char[j]);
	c->kc_info |= KC_NEEDED;
	if (!(c->kc_info & KC_LOADED))
	  f->kf_words_needed += char_words_needed(c);
      } else f->kf_char[j].kc_info &= ~KC_NEEDED;
    /* Load the symbols needed and not yet loaded */
    download_font(f);
  }
}


/* Load a font into the Kyocera */
download_font(fnt)
     struct kyofont *fnt;
{
  register int i;

  /* Make free space if needed */
  if (fnt->kf_words_needed > freewords)
    kyofree(fnt->kf_words_needed);
  freewords -= fnt->kf_words_needed;
  fnt->kf_words_used += fnt->kf_words_needed;

  /* Load needed symbols that are not yet loaded */
  for (i = 0; i < MAXCHARS; i++)
    if ((fnt->kf_char[i].kc_info & KC_NEEDED)
	&& !(fnt->kf_char[i].kc_info & KC_LOADED))
	  load_char(i,fnt);

  /* If font is loaded for the first time set its font attributes */
  if (!(fnt->kf_info & KF_LOADED))
    fprintf(kyo, "!R! FONT %1d; SFA 0,0,P,0;EXIT;",fnt->kf_kyonumber);

  debug("Loaded %d words for font %s, %d\n",
	fnt->kf_words_needed, fnt->kf_name, fnt->kf_kyonumber);
  fnt->kf_words_needed = 0;
  fnt->kf_info |= KF_LOADED;
}

/* Load symbol "ch" from font "fnt" */
load_char(ch,fnt)
     unsigned long ch;
     struct kyofont *fnt;
{
  register unsigned short *bp;
  register int w;
  struct kyochar *c = &(fnt->kf_char[ch]);
  int row,bw;

  /* If symbol is too high it cannot be loaded
   * and should be send as a raster each time it is used
   */
  if (c->kc_height > MAXKYOHEIGHT) {
    c->kc_info |= KC_RASTER;
    return;
  }

  /* Does this symbol exist */
  if (!(c->kc_glyph.l))
    croak("Char %#x not in font %s\n",ch, fnt->kf_name);

  /* Load metrics information about the symbol */
  fprintf(kyo, "!R! LDFC %1d,%1d,%1d,%1d,%1d,%1d,%1d,%1d,0;"
	 ,fnt->kf_kyonumber, visual(ch), c->kc_height, c->kc_width
	 ,32*c->kc_yoffset,32*c->kc_xoffset
	 ,32*c->kc_kyowidth, 16*c->kc_kyowidth);

  /* Load the pixel raster */
  bp = c->kc_glyph.p;
  bw = (c->kc_width + 15) / 16;
  for (row = 0; row < c->kc_height; row++) {
    for (w = 0; w < bw; w++) convert(*bp++);
  }
  fprintf(kyo, "; EXIT;"); fflush(kyo);
  c->kc_info |= KC_LOADED;
}

/* Convert pixel information into the format needed by the Kyocera */
convert(w)
     unsigned short w;
{
  char c;

  c = ((w >> 10) & 077) + 64;
  if (c == 127) c = '/'; putc(c, kyo);
  c = ((w >> 4) & 077) + 64;
  if (c == 127) c = '/'; putc(c, kyo);
  c = (w & 017) + 48; putc(c, kyo);
}

/* Read the information for font "fnt" from file "fname" */
int readpxlfile(fnt,fname)
     struct kyofont *fnt;
     char *fname;
{
  long pxl_id,pxl_dir_ptr;
  long pxlchecksum;
  FILE *f;

  if (!(f = fopen(fname,"r"))) croak("no pxl file %s",fname);
  if ((pxl_id = sget4(f)) != 1001)
    croak("%d bad initial pxl ID; %s doesn't look like a pxl file",
	  pxl_id, fnt->kf_filename);

  debug("OK This is a PXL file\n");
  /* Read the last 5 longs from the pxl file */
  (void) fseek(f, (long)(-5*4), 2);
  pxlchecksum = sget4(f);
  fnt->kf_pxl_mag_val = sget4(f);
  fnt->kf_pxl_design_size = sget4(f);
  pxl_dir_ptr = sget4(f);
  if ((pxl_id = sget4(f)) != 1001)
    croak("%d bad final pxl ID; %s doesn't look like a pxl file",
	  pxl_id, fnt->kf_filename);
  if (pxl_dir_ptr != ftell(f) / 4 - 517)
    croak("%s pxl dir ptr is %x, should be %x",
	  fnt->kf_filename, pxl_dir_ptr, ftell(f) / 4 - 517);
  debug("pxl: checksum %d mag %d designsize %d dir %x\n",
	pxlchecksum,fnt->kf_pxl_mag_val,
	fnt->kf_pxl_design_size,pxl_dir_ptr);

  if (fnt->kf_tfm_checksum & (pxlchecksum != fnt->kf_tfm_checksum)) {
    fprintf(stderr, "Checksum mismatch: %s\n",fnt->kf_filename);
    fprintf(stderr, "   pxl file: %ld, tfm_file: %ld\n",
	    pxlchecksum, fnt->kf_tfm_checksum);
  }

  /* Read the directory */
  (void) fseek(f, pxl_dir_ptr * 4, 0);
  getpxldir(fnt,f);
  debug("     %s max_height=%d\n",fnt->kf_filename,fnt->kf_maxheight);

  /* Read in all the glyphs */
  getglyphs(fnt,f);

  (void) fclose(f);
}

/* Read the pixel directory for font "fnt" from file "f" */
getpxldir(fnt,f)
     struct kyofont *fnt;
     FILE *f;
{
  int i;
  struct kyochar *c;
  double ds = ((double) fnt->kf_s) / ((double) (1 << 20));

  /* Each symbol has an entry of 16 bytes */
  fnt->kf_maxheight = 0;
  for (i = 0; i < MAXCHARS; i++) {
    c = &(fnt->kf_char[i]);
    c->kc_width = sget2(f);
    c->kc_height = sget2(f);
    c->kc_xoffset = sget2(f);
    c->kc_yoffset = sget2(f);
    c->kc_glyph.l = get4(f);
    c->kc_pxlwidth = sget4(f);
    c->kc_texwidth = (long) (((double) c->kc_pxlwidth) * ds);
    c->kc_kyowidth =
		 (c->kc_texwidth * numerator + half_denominator) / denominator;
    /* Determine the maximum height of the font */
    fnt->kf_maxheight = max(fnt->kf_maxheight,c->kc_height);
  }
}

/* Read the raster information for font "fnt" from file "f"
 * The information per symbol is a multiple of 4 bytes
 * The information per symbol that will be loaded is a multiple of 2 bytes
 */
getglyphs(fnt,f)
     struct kyofont *fnt;
     FILE *f;
{
  register int j,row;
  register unsigned short *p;
  int i,fbw,bw;
  struct kyochar *c;

  for (i = 0; i < MAXCHARS; i++) {
    c = &(fnt->kf_char[i]);
    if (c->kc_glyph.l) {
      (void) fseek(f, c->kc_glyph.l * 4, 0);
      fbw = ((c->kc_width + 31) / 32) * 2;
      bw = (c->kc_width + 15) / 16;
      if (!(p = (unsigned short *)malloc(2*bw * c->kc_height)))
	croak("malloc %d",2*bw * c->kc_height);
      c->kc_glyph.p = p;
      for (row = 0; row < c->kc_height; row++) {
	for (j = 0; j < bw; j++) *p++ = get2(f);
	/* get rid of empty trailing bytes */
	for (j = bw; j < fbw; j++) (void) get2(f);
      }
    }
  }
}

/* This procedure is called for every character so it must be fast
 * It returns the TeX width and the device width of symbol "ch"
 */
f_use_char(ch,texwidth,devwidth)
unsigned long ch;
long *texwidth,*devwidth;
{
  register struct kyochar *c = &(curfnt->kf_char[ch]);

  *texwidth = c->kc_texwidth;
  *devwidth = c->kc_kyowidth;
}


/* free words by deleting font(s) using LRU strategy
 * at least "words" words should be free
 */
int kyofree(words)
     int words;
{
  int i,oldtime;
  struct kyofont *oldfont;
  struct preload *oldpr;

  while (words > freewords) {
    oldpr = NULL;
    /* First try preloaded fonts not yet used by this job */
    for (i=0; i<nloaded; i++)
      if (loaded[i].pr_info & PR_LOADED) {
	oldpr = &loaded[i];
	break;
      }
    /* Found one, delete it */
    if (oldpr) {
      oldpr->pr_info &= ~PR_LOADED;
      fprintf(kyo,"!R! DELF %1d;EXIT;",oldpr->pr_font->pr_kyonumber);
      fflush(kyo);
      debug("Left free %ld\n", freewords);
      debug("PRELOADED FONT %s, %d deleted, size %ld\n",
	    oldpr->pr_font->pr_name,
	    oldpr->pr_font->pr_kyonumber,
	    oldpr->pr_font->pr_words_used );
      freewords += oldpr->pr_font->pr_words_used;
    } else {
      /* Try the other fonts now */
      oldfont = NULL;
      oldtime = timestamp;
      for (i = 0; i < nkyofonts; i++)
	if ((kyofonts[i]->kf_info & KF_LOADED)
	    && (kyofonts[i]->kf_timestamp < oldtime)) {
	  oldfont = kyofonts[i];
	  oldtime = oldfont->kf_timestamp;
	}
      if (!oldfont) croak("kyofree bug");
      oldfont->kf_info &= ~KF_LOADED;
      oldfont->kf_words_needed = KF_MIN;
      oldfont->kf_words_used = 0;
      fprintf(kyo,"!R! DELF %1d;EXIT;",oldfont->kf_kyonumber);
      fflush(kyo);
      debug("Left free %ld\n", freewords);
      debug("FONT %s, %d deleted, size %ld\n",
	    oldfont->kf_name,
	    oldfont->kf_kyonumber,
	    oldfont->kf_words_used);
      freewords += oldfont->kf_words_used;
      for (i = 0; i < MAXCHARS; i++) {
	oldfont->kf_char[i].kc_info &= ~KC_LOADED;
	if (oldfont->kf_char[i].kc_info & KC_NEEDED)
	  oldfont->kf_words_needed+=char_words_needed(&(oldfont->kf_char[i]));
      }
    }
  }
}