/*
 *	This program is Copyright (C) 1987 by the Board of Trustees of the
 *	University of Illinois, and by the author Dirk Grunwald.
 */

#include <assert.h>
#include <stdio.h>
#include <ctype.h>

#include "../h/types.h"
#include "../h/font.h"

#include "../h/conv.h"
#include "../h/dvi.h"
#include "../h/dviclass.h"
#include "../h/dvicodes.h"
#include "../h/postamble.h"
#include "../h/search.h"
#include "../h/fio.h"

/* X11 related files */

int	DviBlackness = -1;
int	DviDpi = 300;
int DviMag = 1000;
int DviDsz = 1000;
char	*ProgName;
int ShowSnf = 0;
int UserMag = 1000;

long Numerator = 25400000;
long Denomonator = 473628272;

Conv Conversion;
float DviFontTfmScale;

#define	GF_SHRUNK	GF_USR0
#define	GLYPH_SHRUNK(g) ((g) -> g_flags & GF_SHRUNK)
#define SHRUNK_GLYPH_BYTES_WIDE(g) ((((g) -> g_width + 15) >> 4) * 2)
#define DEFAULT_BLACKNESS 3

#define TWOTO16	0x10000
#define TWOTO24 16777216
#define PTPI 72.27

main(argc, argv)
int argc;
char **argv;
{
  char *name = 0;
  char *dev = "canon";
  char *fname = 0;
  int arg;
  extern void processFont();

  struct font *theFont;

  ProgName = argv[0];

  for (arg = 1; arg < argc; arg++) {
    char *str = argv[arg];
    if ( strcmp(str, "-scaled") == 0 ) {
      arg++;
      DviMag = atoi( argv[arg] );
    }
    else  if ( strcmp(str, "-dpi") == 0 ) {
      arg++;
      DviDpi = atoi( argv[arg] );
    }
    else  if ( strcmp(str, "-size") == 0 ) {
      arg++;
      DviDsz = atoi( argv[arg] );
    }
    else  if ( strcmp(str, "-dev") == 0 ) {
      arg++;
      dev = argv[arg];
    }
    else  if ( strcmp(str, "-show") == 0 ) {
      ShowSnf = !ShowSnf;
    }
    else  if ( strcmp(str, "-mag") == 0 ) {
      arg++;
      UserMag = atoi(argv[arg]);
    }
    else  if ( strcmp(str, "-b") == 0 ) {
      arg++;
      DviBlackness = atoi(argv[arg]);
    }
    else if ( str[0] != '-' ) {
      name = argv[arg];
      
      if ( name == 0 ) {
	fprintf(stderr,"%s: you must specify a font name\n", ProgName);
	exit(1);
      }

/* We read the font in at mag and then step it down to user mag, so
   assume here that usermag is 1000 */

      SetConversion(DviDpi, 1000, Numerator, Denomonator, 1000);
      
      theFont = GetFont(name, DviMag, DviDsz, dev, &fname);
      if ( theFont == 0 ) {
	fprintf(stderr,"Unable to locate ``%s''\n", name);
	continue;
      }

      if ( fname != 0 ) {
	fprintf(stderr,"%s: returned name is %s\n", ProgName, fname);
	fprintf(stderr,"%s: which lives in %s\n",
		ProgName, theFont -> f_path);
      }

/*      DviFontTfmScale = fromSP( theFont -> f_design_size); */
      DviFontTfmScale = ((double)theFont -> f_design_size);
      DviFontTfmScale *= 1000;
      DviFontTfmScale /= (double) TWOTO24;

      processFont( theFont, name );

    }
    else {
      fprintf(stderr,"%s: don't grok ``%s''\n", ProgName, argv[arg]);
      exit(1);
    }
  }
  exit(0);
}

/*
 *	The following routine is used to shrink a glyph by some
 *	shrink factor (in the width & height dimension).
 *
 *	These shrunken glyphs are more appropriate for previewers.
 *
 *	To do this, we simple scan the original raster, counting the
 *	number of pixels within a given area. If the number of on pixels is
 *	at least twice the total number of pixels, we turn the pixel in
 *	the shrunken glyph ON, else OFF.
 *
 *	We use a lookup table to hasten the process of counting pixels.
 *
 *	The method used should be independent of byte-order (I think).
 *
 *	You need to define two types. One should be 32-bits long, and
 *	the other 16 bits long.
 */

typedef unsigned char BITS_8;
typedef unsigned short BITS_16;
typedef unsigned long BITS_32;

#define LOOKUP_BYTES	256
#define	LOOKUP_BITS	8
#define LOOKUP_MASK	0xff

static char dviLookUpTable[LOOKUP_BYTES];
static char tableNotInitialized = 1;

struct glyph *
dviShrinkGlyph(gly, font, rotation,shrinkH, shrinkW, blackness, killOld)
struct glyph *gly;
struct font *font;
int rotation;
int shrinkH;
int shrinkW;
int blackness;
int killOld;
{
    int shrunkHeight;
    int shrunkWidth;
    int glyphWide;
    int glyphHigh;
    int glyphAdvance;
    int shrunkAdvance;

    int bytesWide;
    int shrunkBytesWide;

    struct glyph *ngly;

    BITS_16 *shrunkRaster;
    int rasterSize;

    long width;
    long dvimag;

    int x,y;
    BITS_8 *cp;
    BITS_8 *ptr;

    BITS_32 shrinkMask;

    int sampleSize;

    if (gly == 0 || !HASRASTER(gly) || (gly -> g_width < 0) ) {
	return(0);
    }

    if (gly -> g_raster == 0) {
	gly-> g_raster = RASTER(gly, font, rotation);
    }

#ifdef UNDEF
    if ( shrinkH == 1 ) {
      return( gly );
    }
#endif

/*
 *	Initialize the lookup table of the number of bits in a given byte
 */

    if (tableNotInitialized) {
	register int i;
	register int j;
	register int k;
	register int acc;
	for (i = 0; i < LOOKUP_BYTES; i++) {
	    j = i;
	    acc = 0;
	    for (k = 0; j != 0 && k < LOOKUP_BITS ; k++) {
		acc += (j & 1);
		j >>= 1;
	    }
	    dviLookUpTable[i] = acc;
	}
	tableNotInitialized = 0;
    }

    glyphHigh = gly -> g_height;
    glyphWide = gly -> g_width;

/*
 *	The following computes the ``escapement'' or the TFMwidth in
 *	pixels. This converted to the DWIDTH in the bdf files.
 *
 *	This is a screwy point with Toreks library. He changes
 *	the values of the design_size
 *
 */
    dvimag = font -> f_design_size / 16;
    dvimag *= font -> f_scaled;
    dvimag /= 1000;
    width = ScaleOneWidth( gly -> g_rawtfmwidth, dvimag);
    gly -> g_pixwidth = fromSP( width );
    glyphAdvance = (gly -> g_pixwidth);

    shrunkHeight = (glyphHigh + shrinkH - 1) / shrinkH;
    shrunkWidth =  (glyphWide + shrinkW - 1) / shrinkW;
    shrunkAdvance =  (glyphAdvance + shrinkW - 1) / shrinkW;

    bytesWide = (gly -> g_width + 7) >> 3;

    shrunkBytesWide = ((shrunkWidth + 7) >> 3);

    rasterSize = (shrunkHeight + 1) * shrunkBytesWide;
    shrunkRaster = (BITS_16 *) malloc(rasterSize);
    bzero(shrunkRaster, rasterSize);
    ptr = (BITS_8 *) shrunkRaster;
    
    if (shrunkRaster == NULL) {
	fprintf(stderr, "Out of memory!\n");
	exit(1);
    }

    for (y = 0; y < glyphHigh; y+= shrinkH) {
	cp = (BITS_8 *) ptr;
	shrinkMask = 0x80;
	for (x = 0; x < glyphWide; x += shrinkW) {
	    int i;
	    int samples;
	    BITS_8 *baseP;
	    int upper;
	    register int thisShrinkW;

	    baseP = (BITS_8 *) gly -> g_raster + (y * bytesWide);

/*
 *	Set the upper limit on the height iteration so we dont count
 *	off the end of the raster
 */

	    upper = y + shrinkH;
	    if (upper > glyphHigh) {
		upper = glyphHigh;
	    }

	    if (x + shrinkW > glyphWide) {
		thisShrinkW = glyphWide - x;
	    } else {
		thisShrinkW = shrinkW;
	    }

	    samples = 0;
	    sampleSize = thisShrinkW * shrinkH;

	    for (i = y; i < upper; i++) {
		register int acc;
		register BITS_8 *p;
		register BITS_8 *ep;
/*
 *	Determine how many bytes our shrink window crosses (we might
 *	overlap on byte-edges)
 */

		p = baseP + (x >> 3);
		ep = baseP + ( (x + thisShrinkW - 1) >> 3);
		baseP += bytesWide;

/*
 *	stuff everything in the accumulator
 */

		acc = 0;
		while (p <= ep) {
		    acc = ((acc << 8) & 0xff) | *p;
		    p++;
		}

/*
 *	clean off the right hand-side of extra bits, then clean off
 *	the left hand side of extra bits, and then count them.
 */

		acc = acc >> ( 7 - ((x + thisShrinkW - 1) & 0x7));
		acc &= ~(-1 << thisShrinkW);
		while (acc != 0) {
		    samples += dviLookUpTable[ acc & LOOKUP_MASK ];
		    acc >>= LOOKUP_BITS;
		}
	    }
/*
 *	If at least 1/blackness of the bits are on, treat this entire sample as
 *	being on.
 */

	    if ((samples * blackness) >= sampleSize) {
		*ptr |= shrinkMask;
	    } else {
		*ptr &= ~ shrinkMask;
	    }
	    shrinkMask >>= 1;
	    if (shrinkMask == 0) {
		shrinkMask = 0x80;
		ptr ++;
	    }
	}
	ptr = (BITS_8 *) (cp + shrunkBytesWide);
    }

/*
 *	Build a new glyph from the shrunken raster
 */

    if (ShowSnf) {
      printf("Old glyph:\n");
      seeGlyph(gly -> g_raster, glyphHigh, bytesWide);
      printf("New glyph:\n");
      seeGlyph(shrunkRaster, shrunkHeight, shrunkBytesWide);
    }

    ngly = (struct glyph *) malloc(sizeof(struct glyph));
    bzero(ngly, sizeof(struct glyph));

    ngly -> g_raster = (char * ) shrunkRaster;
    ngly -> g_un.g_integer = shrunkBytesWide;
    ngly -> g_width = shrunkWidth;
    ngly -> g_pixwidth = shrunkAdvance;
    ngly -> g_height = shrunkHeight;

    ngly -> g_xorigin = gly -> g_xorigin / shrinkH;
    ngly -> g_yorigin = gly -> g_yorigin / shrinkW;

    ngly -> g_flags |= GF_SHRUNK;	/* yes, its been shrunk */

    if ( killOld && gly -> g_raster ) {
      free ( gly -> g_raster );
      gly -> g_raster = 0;
    }

    return(ngly);
}

seeGlyph(c, h, w)
char *c;
int h;
int w;
{
    int i,j;

    for (i = 0; i < h; i++ ) {
	for (j = 0; j < w; j++) {
	    int k;
	    register int ch;
	    register int m;
	    char str[9];

	    ch = *(c++);
	    m = 0x80;
	    for (k = 0; k < 8; k++) {
		str[k] = ( (ch & m) ? '#' : '-' );
		m >>= 1;
	    }
	    str[8] = 0;
	    printf("%s", str);
	}
	printf("\n");
    }
}

printhex(f,n)
FILE *f;
int n;
{
  fputc("0123456789abcdef"[(n >> 4) & 0xf], f);
  fputc("0123456789abcdef"[n & 0xf], f);
}

/*	SNF related junk */

void
processFont(f, name)
struct font *f;
char *name;
{
  struct glyph *g;
  int c;
  int blackness;

  int maxOverlap;
  int nnonexistchars;

#define MAX_CHARACTER	127

  int glyphBytes[ MAX_CHARACTER + 1];
  struct glyph *shrunkGlyphs[ MAX_CHARACTER + 1];

  int totalGlyphs;
  int pointSize;
  int shrink;

  int min_width = TWOTO16;
  int max_width = -TWOTO16;

  int min_height = TWOTO16;
  int max_height = -TWOTO16;

  int min_xorigin = TWOTO16;
  int max_xorigin = -TWOTO16;

  int min_yorigin = TWOTO16;
  int max_yorigin = -TWOTO16;

#define MIN(x,y) ((x < y) ? (x) : (y))
#define MAX(x,y) ((x < y) ? (y) : (x))

#ifdef __STDC__
#define MINMAX(x,y) { min_##x = MIN(min_##x,y); max_##x = MAX(max_##x,y); }
#else
#define MINMAX(x,y) { min_/**/x = MIN(min_/**/x,y); max_/**/x = MAX(max_/**/x,y); }
#endif

  char newFileName[256];
  char newName[256];
  FILE *fontOut;
  int i;

  double suffMag = (double) DviMag / (double) DviDsz;
  int scaled = (suffMag * 1000 + 0.5);
  int suff = (DviDpi * UserMag * DviMag) / (1000 * 1000);

  if ( f -> f_design_size == 0 ) {
    return;
  }
    
  shrink = 1000 / UserMag ;
  if (shrink < 1 ) {
    shrink = 1;
  }
  else if ( shrink > 10 ) {
    shrink = 10;
  }
  
  blackness = DviBlackness;
  blackness = (blackness < 1) ? DEFAULT_BLACKNESS : blackness;

  sprintf(newName, "%s.%d", name, suff);
  sprintf(newFileName, "%s.bdf", newName);

  fontOut = fopen(newFileName,"w");

  if (! fontOut  ) {
    perror("fopen");
    exit(1);
  }

  totalGlyphs = 0;

  for( c = f -> f_lowch; c < f -> f_highch; c++ ) {
    g = GLYPH(f,c);
    shrunkGlyphs[c] = 0;

    if (GVALID(g) && HASRASTER(g)) {
	struct glyph *s;

	s = dviShrinkGlyph( g, f, ROT_NORM, shrink, shrink, blackness, 1);
	shrunkGlyphs[c] = s;

	if (s == 0 )
	  continue;

	totalGlyphs++;

	MINMAX(width, s->g_width);
	MINMAX(height, s->g_height);
	MINMAX(xorigin, s->g_xorigin);
	MINMAX(yorigin, s->g_yorigin);

    }
  }

  fprintf(fontOut, "STARTFONT 2.1\n");
  fprintf(fontOut,
	  "COMMENT This font image translated from Metafont\n");
  fprintf(fontOut, "COMMENT Input file was ``%s''\n", name);
  fprintf(fontOut, "COMMENT DPI was ``%d'', usermag ``%d'', blackness ``%d''\n",
	  DviDpi, UserMag, blackness);
  fprintf(fontOut, "COMMENT shrink was %d\n", shrink);

  fprintf(fontOut, "FONT %s\n", newName);

  pointSize = (f -> f_design_size) / (TWOTO16 * 16);
  fprintf(fontOut, "SIZE %d %d %d\n", pointSize, suff, suff);

  fprintf(fontOut, "FONTBOUNDINGBOX %d %d %d %d\n",
	  max_width + 1, max_height,
	  -max_xorigin, max_yorigin - max_height + 1);

  /* These follow the X conventions */

  fprintf(fontOut,"STARTPROPERTIES 3\n");
  fprintf(fontOut,"FONT_ASCENT %d\n",
	  max_height - min_yorigin);
  fprintf(fontOut,"FONT_DESCENT %d\n",
	  min_yorigin);
  fprintf(fontOut,"COPYRIGHT \"Public Domain\"\n");
  fprintf(fontOut,"ENDPROPERTIES\n");
  fprintf(fontOut,"CHARS %d\n", totalGlyphs);

  for( c = f -> f_lowch; c < f -> f_highch; c++ ) {
    if ( shrunkGlyphs[c] != 0 ) {
      int column;
      int width;
      float swidth;
      int bytes;
      int byte;
      char *p;
      struct glyph *s = shrunkGlyphs[c];

      if ( s -> g_raster == 0 ) {
	continue;	/* to next one */
      }

      fprintf(fontOut, "STARTCHAR C%03d\n",c);
      fprintf(fontOut, "ENCODING %d\n",c);

      /* I don't know why it's off by one, but it is.. */

      width = s -> g_pixwidth;

      swidth = (double) ( width * 72000 ) / (double) ( pointSize * DviDpi );
      fprintf(fontOut, "SWIDTH %d %d\n", (int) swidth, 0);
      fprintf(fontOut, "DWIDTH %d %d\n", width, 0);

      fprintf(fontOut, "BBX %d %d %d %d\n",
	      s -> g_width,	/* width, height of bounding box */
	      s -> g_height,
	      -s -> g_xorigin,	/* offset of origin w.r.t. lower left corner*/
	      s -> g_yorigin - s -> g_height + 1);
      fprintf(fontOut, "BITMAP\n");

      column = 0;
      width = s -> g_un.g_integer;
      bytes = width * s -> g_height;
      for( p = s -> g_raster, byte = 0; byte < bytes; byte++, p++, column++ ) {
	if (column >= width) {
	  fprintf(fontOut,"\n");
	  column = 0;
	}
	printhex(fontOut,*p);
      }
      if (column == width) {
	fprintf(fontOut,"\n");
      }
      fprintf(fontOut, "ENDCHAR\n");
    }
  }
  fprintf(fontOut, "ENDFONT\n");
}
