/*  mff.c 2.9.0 92/07/06 - run a METAFONT-GFtoPK job to make a font
-----------------------------------------------------------------------
    This program copyright (c) 1990, 1991 Damian Cugley.  
    It is provided for free on an "as-is" basis.
    See the file COPYING for more information.
    See mff(1) for more info on using this program
-----------------------------------------------------------------------
 *
 * 2.0 - C version of shellscript
 *  Created Damian Cugley <pdc@oxford.prg> Sat 13 Jan 1990
 * 2.1
 *  DPIs, magnification and factor can be lists of floats
 *  - pdc Sat 27 Jan 1990
 *  - pdc Mon 29 Jan 1990
 * 2.2 - pdc Tue 30 Jan 1990
 *  Define size using font_size rather than design_size#
 *  (more "standard")
 * 2.3 - pdc Fri 30 Mar 1990
 *  If the mode-def starts with `"', use `smode' rather than `mode'.
 * 2.4 - pdc Mon 16 Jul 1990
 *  2 new options: -J and -j; act like -I, -i: extra user-defined suffixes
 * 2.5 - pdc Sun. 16 Sept. 1990
 *  Now uses a searchpath from env. var $MFFPATH (default = $MFINPUTS).
 * 2.6 - pdc Fri. 1 Mar. 1991
 *  Wrapping up for export; removed some old junk code.
 * 2.7 - pdc Wed. 24 Apr. 1991
 *  output from -n is /bin/sh commands; -v output has #
 * 2.8 - pdc Tue. 21 May 1991
 *  Renamed mff.  
 *    Can understand sizes with "p" as decimal point.
 *    -R option to retain GF file.
 *    The input of an empty file hack is removed.
 *    -S option to not parse size part [not needed before]
 *    some support for MS-DOS filesystems (Adrian's Atari ST)
 */

#include <sys/param.h>		/* MAXPATHLEN */
#include <sys/types.h>		/* etc. */
#include <errno.h>

#include <ctype.h>		/* isdigit */
#include <math.h>		/* to calculate magsteps */
#ifdef BSD
#  include <sys/fcntl.h>	/* O_RDONLY etc */
#else
#  include <fcntl.h>
#endif

#include "stdc.h"
#include "xstdio.h"		/* <stdio.h> plus prototypes */

#include "config.h"
#include "argloop.h"
#include "fatal.h"
#include "searchpath.h"
#include "assoc.h"
#include "fx.h"
#include "strmisc.h"		/* includes <string(s).h> */
#include "magstep.h"

#include "version.h"		/* version number goes here */

#ifdef FANCY_MALLOC
#  include <malloc.h>
int mallopt ARGS((int, int));
int mallocmap ARGS((void));
#endif

#ifndef MAXPATHLEN
#  define MAXPATHLEN 1024
#endif
#define MAX_MF_LINE	512
#define MAX_BASENAME	256
#define MAX_TEMPLATE	1024

static const char	
		what[] = MFF_VERSION
#ifdef __STDC__
  		" (compiled " __TIME__ " " __DATE__ ")"
#endif
  		,
		*version = what + 5;

#define TRUE	1
#define FALSE	0


/*			GLOBAL VARIABLES		  */

static const char 
       	       *mffpath,	/* the MFFPATH environment variable */
  	        suffix[] = ".mff";

int		dryrun = FALSE;	/* if true, don't really do anything  */
int		verbose = 0; /* print more messages */
int		retain_gf = FALSE; /* don't (attempt to) remove GF file */
int		no_parse = FALSE; /* don't parse size etc. at all */

static Assoclist
	 	mode_alist,	/* given "300", return "mode:=CanonCX" */
  		weight_alist,	/* given "m", return "weight:=1.25" */
		cndnsd_alist,	/* given "xc", return "hratio:=0.5" */
		italic_alist,	/* given "pi", return "slant:=1/5" */
		extra_alist;	/* Extra Alist for user's extra suffixes */

/*  Strings set by -z, -w, -h, -i, -g, -p options -- or NULL  */
static char 
       *ovr_mode, *ovr_weight, *ovr_cndnsd, *ovr_italic, *ovr_extra,
       *ovr_driver, *ovr_pkpxl;	

struct float_node
{
  struct float_node *next;
  float val;
} *dpi_list, *atsize_list, *mag_list, *pxlfactor_list;

static float	design_sizeF = 10.0;	/* design-size in pt */


static int	pxlB = FALSE;	/* If true, multiply PK suffix by */
				/* |pxlfactorF| */

/*  Templates  */
static char    *mf_template, *gfto_template, *tfm_template, *pk_template;

static const char    *mode,
	       *driver,		/* either |basename| or |ovr_driver| */
	       *weight, *cndnsd, *italic,
	       *extra,		/* - pdc Mon 16 Jul 1990 */
  	       *unit,		/* either "pt" or "mm" */
	       *pkpxl;		/* set by -p */
static char	basename[MAX_BASENAME];

struct eqn_node
{
  struct eqn_node *next;
  char *val;
} *eqns, *eqns2;			/* set with -e and -E resp. */


/*  Both of the following set their arg to NULL:  */

#define DISPOSE(L) do { char *t_; \
    while (L) { t_ = (char *)(L); (L) = (L)->next; xfree(t_); } \
} while (0)

#define FREE_DISPOSE(L) do { struct eqn_node *t_; \
    while (L) { t_ = (struct eqn_node *)(L); (L) = (L)->next; \
		  xfree((addr)(t_->val)); xfree((addr)t_); } \
} while (0)

/*  
 *  The do .. while(0) idiom is intended to get around the fact
 *  that a compound statement can't be followed by a semicolon
 *  when it is the "then" part of an if-then-else.
 *
 *  Unfortunately lint does not recognise this and produces
 *  spurious error messages.
 */

#ifndef lint
#  define NEW(T)	((T *)xmalloc(sizeof(T)))
#else
#  define NEW(T)	((T *)0)
#endif



/*
 *  Simple template routine.
 *  This is the bit that handles %-substitutions.
 *
 *  The table of substitution strings only has entries for u&lc letters
 */
static const char *table[52];

/*  
 *  These two lines will need changing if using a character
 *  encoding in which the letters aren't contiguous.  
 */
#define lc_ord(X)	((X) - 'a')
#define uc_ord(X)	((X) - 'A')

/*
 *  Set entry in table -- X is a character, Y is a char *
 */
#define lc_set(X,Y)	table[lc_ord(X)] = (Y)
#define uc_set(X,Y)	table[uc_ord(X) + 26] = (Y)

/*
 *  Print string |format| into |buf|, making %-subs from |table|.
 *  If a %f substitution is made, the global variable |f_sub| is 
 *  incremented.  (What a hack!!)
 */

int f_sub = 0;

char *
template(buf, format)
     char      *buf, *format;
{
  char	*b = buf;

  for (; *format; format++)
    if (*format != '%')
      *b++ = *format;
    else if (*++format == '%')	/* % followed by %? */
      *b++ = '%';
    else if (isalpha(*format))	/* % followed by letter? */
      {
	const char *sub;

	if (sub = table[islower(*format)
			? lc_ord(*format)
			: uc_ord(*format) + 26])
	  {
	    if (*format == 'f') f_sub++;
	    while (*sub) *b++ = *sub++;
	  }
	else
	  fprintf(stderr, "Warning -- %%%c ignored.\n", *format);
      }
    else if (!*format)		/* % at end of string? */
      {
	fprintf(stderr, "Warning -- %% at end of string ignored.\n");
	--format;		
      }
    else			/* % followed by non-letter */
      fprintf(stderr, "Warning -- %%%c skipped.\n", *format);
  *b++ = '\0';
  return buf;
}

/*
 *  OPERATIONS ON FILES
 */

extern int	unlink ARGS((const char *));

static inline int		/* return nonzero iff file exists */
file_exists_p(s)
     const char *s;
{
  int access ARGS((const char *name, int flags));

  return (access((s), 0) >= 0);
}

static inline void		/* errors are fatal */
xmkdir(s)
     const char *s;
{
  if (mkdir(s, 0777) < 0)
    pfatalf("could not create directory %s", s);
}

/*
 *  Rename file called "old" to be called "new".
 *  Will copy to new name and unlink if "new" is on a different filesystem.
 *  (This suggested by Andrew Haylett <ajh@gec-mrc.co.uk> of
 *  GEC-Marconi Research.)
 */
static inline int		/* return <0 iff unsuccessful */
rename(old, new)
     const char *old, *new;
{
  extern int	link ARGS((const char *, const char *));

  if (file_exists_p(old, 0) < 0)
    pfatalf("%s should exist but does not", old);
  else
    {
      unlink(new);		/* ignore any error */
      if (link(old, new) < 0)
	{
	  if (errno != EXDEV)
	    pfatalf("%s cannot be linked to %s", old, new);
	  else
	    {
	      /* attempted to cross-link between devices; copy instead */
	      int read ARGS((int, void *, sizeof_t));
	      int write ARGS((int, void *, sizeof_t));
	      int open ARGS((const char *, int));
	      int creat ARGS((const char *, int));
	      int close ARGS((int));
	      
	      int in, out;	/* file descriptors */
	      char buf[2048];	/* a sort of beige */
	      int nbytes;	/* byte count from read() */

	      if ((in = open(old, O_RDONLY)) < 0)
		pfatalf("can't read %s", old);
	      if ((out = creat(new, 0666)) < 0)
		pfatalf("can't create %s", new);
	      while ((nbytes = read(in, buf, sizeof buf)) > 0)
		if (write(out, buf, nbytes) != nbytes)
		  pfatalf("could not write %d bytes to %s", nbytes, new);
	      if (nbytes < 0)
		pfatalf("could not read from %s", old);
	      close(in);
	      close(out);
	    }
	}
      if (unlink(old) < 0)
	pfatalf("%s cannot be removed", old);
    }
  return 0;
}

static void
install_file(file, tplate, tplate2)
     char      *file, *tplate, *tplate2;
{
  char scratch[MAXPATHLEN];
  
  f_sub = 0;
  (void)template(scratch, tplate);
  if (!f_sub)
    {
      char *t = scratch + strlen(scratch);
      if (t > scratch && *(t - 1) != dir_sep_ch)
	*t++ = dir_sep_ch;
      (void)template(t, tplate2);
    }
  
  if (dryrun)
    {
      printf("mv %s %s\n", file, scratch);
      return;
    }
  else if (rename(file, scratch) >= 0)
    return;
  
  if (errno != ENOENT) 
    pfatalf("can't rename %s to %s", file, scratch);
  else
    {
      /*
       *  On some brain-damaged systems, names of the form foo.96pk
       *  can't be accomodated and so they are split into a directory/name
       *  pair -- on MS-DOS systems, either say  96PK\FOO or FOO\96PK.
       *  In this case, we attempt to create the missing directory part.
       *  This stuff may also be necessary for USG systems with their
       *  14-character filenames; I don't know.
       *
       *  Assume path in form  "xxx/f/g"  where f & g are
       *  simple filenames and xxx is a path.
       *  If s/f does not exist but xxx does, then create directory xxx/f
       *  and try again.
       */

      char *sl1 = (char *)0, *sl2 = (char *)0; /* positions of slashes */

      {
	register char *s;

	for (s = scratch; *s; s++)
	  if (isdirsep(*s))
	    sl2 = sl1, sl1 = s;
      }
      
      if (sl2
	  && (*sl1 = '\0', !file_exists_p(scratch))
	  && (*sl2 = '\0', file_exists_p(scratch)))
	{
	  /*  xxx must be a directory, else would have got ENOTDIR earlier  */
	  /*  Not that this matters: mkdir would simply fail anyway  */

	  *sl2 = dir_sep_ch;	/* "xxx/f" */
	  if (verbose > 1)
	    printf("# creating directory %s\n", scratch);
	  xmkdir(scratch);	/* create xxx/f */
	  *sl1 = dir_sep_ch;	/* now "xxx/f/g" again */
		
	  if (rename(file, scratch) < 0) 
	    {
	      *sl1 = '\0';
	      pfatalf("created %s but can't rename %s to %s",
		      scratch, file, sl1 + 1);
	    }
	}
      else
	{
	  *sl1 = *sl2 = dir_sep_ch;
	  pfatalf("don't know how to install %s as %s", file, scratch);
	}
    }
}

static void
remove_file(file)
     const char      *file;
{
  if (dryrun)
    printf("rm %s\n", file);
  else
    if (unlink(file) < 0) pfatalf(file);
}




/*
 *     CREATE A FONT
 */

/*  X is evaluated twice in fol. */
#define is_one_char_string(X,C)	(*(X) == (C) && !(X)[1])
#define is_just_hyphen(X)	is_one_char_string((X), '-')

#define SEMI()		(*s++ = ';', *s++ = ' ')
#define APPEND(S)	for (e = (S); *e; *s++ = *e++) ;	
#define ADDEQN(E)	if (E) { APPEND(E); SEMI(); }

static char *
mf_script(mode_name, magF)
     char      *mode_name;
     double 	magF;
{
  char *s;
  const char *e;
  static char the_line[MAX_MF_LINE];
  struct eqn_node *p;

  if (no_parse)
    {
      sprintf(the_line, "\\%s = %s; mag = %g; ",
	      (mode_name[0] == '"' ? "smode" : "mode"),
	      mode_name, magF);
      s = the_line + strlen(the_line);
    }
  else
    {
      sprintf(the_line, "\\%s = %s; mag = %g; font_size %g%s#; ",
	      (mode_name[0] == '"' ? "smode" : "mode"),
	      mode_name, magF, design_sizeF, unit);
      s = the_line + strlen(the_line);

      ADDEQN(weight); ADDEQN(cndnsd); ADDEQN(italic); ADDEQN(extra);
    }
  
  for (p = eqns; p; p = p->next)
    ADDEQN(p->val);
  if (driver)
    { APPEND("input "); APPEND(driver); SEMI(); }
  for (p = eqns2; p; p = p->next)
    ADDEQN(p->val);
  APPEND("bye.");
  *s = '\0';
  return the_line;
}


#define int_round(X)	((int)((X) + 0.5))

static void
do_one_font(font_name, mode_name, dpiF, magF, pxlfactorF)
     char      *font_name;
     double	dpiF, magF, pxlfactorF;
     char      *mode_name;
{
  static char	kdpiS[20], dpiS[20], mdpiS[20], scaleS[20], factorS[20];
  	/* These are static purely for the sake of -D */
  char 		tfname[MAX_BASENAME];	/* place to generate non-directory filenames */
  char		scratch[MAX_TEMPLATE];

  if (verbose) printf("#{{{ %s %s %g %g %g\n",
		      font_name, mode_name, dpiF, magF, pxlfactorF);
  if (pxlB)
    sprintf(kdpiS, "%d", int_round(pxlfactorF * magF * dpiF));
  else
    sprintf(kdpiS, "%d", int_round(magF * dpiF));

  sprintf( dpiS, 	"%d", int_round(    dpiF		));
  sprintf( mdpiS, 	"%d", int_round(    magF * dpiF		));
  sprintf( scaleS, 	"%d", int_round(    magF * 1000.0	));
  sprintf( factorS, 	"%d", int_round(    pxlfactorF * 1000.0	));

  lc_set('b', basename);
  uc_set('D', mdpiS);	/* magnified DPI, yes? */
  lc_set('d', dpiS);	
  lc_set('f', font_name);	
  lc_set('g', driver);	
  lc_set('k', factorS); /* pxl/gf ratio, times 1000 */
  lc_set('n', kdpiS);	
  lc_set('p', pkpxl);	
  lc_set('s', scaleS);	
  lc_set('u', unit);
  lc_set('z', mode_name);	

  if (mf_template)
    {
      if (dryrun)
	printf("%s '%s'\n", template(scratch, mf_template),
	       mf_script(mode_name, magF));
      else
	{
	  char *t = mf_script(mode_name, magF);
 	  fx(template(scratch, mf_template), 1, &t);
	}
      if (tfm_template)
	{
	  sprintf(tfname, "%s.tfm", driver);
	  install_file(tfname, tfm_template, "%f.tfm");
	}
    }
  
  if (gfto_template)
    {
      char gffilename[MAX_BASENAME], *gfargs[2];
#ifdef SHORT_FILENAMES
      /*
       *  On systems which truncate the part of a name follwoing a "."
       *  to something unreasonably short, the files FOO.300PK and FOO.300GF
       *  may not be distinct.  Usually there will be some convention
       *  for splitting names into a directory+filename -- FOOBAR12/300PK,
       *  for example.
       *  I am assuming that GFtoPK will be willing to produce
       *  its output as FOO.PK.  This is then renamed as per the
       *  PK file template (-P option).
       */
      char pkfilename[10];
      
      sprintf(gffilename, "%s.%sgf", driver, mdpiS);
      sprintf(pkfilename, "%s.%s", driver, pkpxl);
      if (dryrun)
	printf("%s %s %s\n", template(scratch, gfto_template),
	       gffilename, pkfilename);
      else
	{
	  gfargs[0] = gffilename, gfargs[1] = pkfilename;
 	  fx(template(scratch, gfto_template), 2, gfargs);
	}
      if (pk_template)
	{
	  static char tp[] = "%f?%n%p";
	  tp[2] = dir_sep_ch;
	  install_file(pkfilename, pk_template, tp);
	}
#else /* !SHORT_NAMES: */
      sprintf(gffilename, "%s.%sgf", driver, mdpiS);
      
      if (dryrun)
	printf("%s %s\n", template(scratch, gfto_template), gffilename);
      else
	{
	  gfargs[0] = gffilename;
	  fx(template(scratch, gfto_template), 1, gfargs);
	}
      if (pk_template)
	{
	  sprintf(tfname, "%s.%s%s", driver, kdpiS, pkpxl);
	  install_file(tfname, pk_template, "%f.%n%p");
	}
#endif /* SHORT_NAMES */
      if (!retain_gf)
	  remove_file(gffilename);
    }
  if (verbose) printf("#}}}\n");
}

/*
 *  Set up everything (italicness etc etc) for one font name and
 *  make fonts.  This is where the parsing is done.
 */
static void
do_font(fn)
     const char *fn;
{
  struct float_node *pxl, *mag, *dpi;
  static double ascii2float ARGS((const char *s));
  
  const char *p, *q; 		/* pointers into |fn| */
  char *b;			/* pointer into |basename| or |canonical| */
  char *canonical;		/* font name with any hyphen dealt with */
  int had_decimal = FALSE;	/* TRUE when had P or m representing
				   a decimal point. */
  char size_scratch[20];	/* size suffix copied into here */
  
  /*
   *  Find the design_size:
   */
  for (p = fn; *p; ++p)		
    ;
  /* p now points to the NUL at the end of fn */
  if (p == fn) syntaxf("font name is empty");
  canonical = (char *)xmalloc(p - fn + 1);
  q = p;			/* get ready to scan from end of |fn| */

  if (!no_parse)
    {
      /*
       *  scan the SIZE part.
       *  |b| points to last character added to size_scratch array
       *  |q| points after first unscanned char in fn
       *  For the sake of ANSI I avoid having |q| point to left of |fn|.
       *
       *  A `p' is recognized as a decimal point iff there is a digit on 
       *  side of it -- 10p5 but not 10p or p5.
       *  An `m' is recognized as a decimal point iff there is a digit
       *  to its left -- 10m5 or 10m but not m5.
       *  Note that this means that when using my Malvern font nmames,
       *  neither "m" nor "p" can be used as encoding codes.  That's life.
       */
      b = size_scratch + sizeof size_scratch; *--b = '\0';
      unit = "pt";
      while (1)
	{
	  if (verbose > 3) printf("## <<%s>> <<%s>> <<%s>>\n", fn, q, b);
	  if (b == size_scratch || q == fn) 
	    break;
	  else if (!had_decimal && 
		   ((*(q - 1) == 'p' && *b)
				/* (*b != 0) means at least 1 digit */
		    || *(q - 1) == 'm')
		   && q > fn + 1 && isdigit(*(q - 2)))
	    {
	      had_decimal = TRUE;
	      *--b = '.', --q;
	      unit = (*q == 'p' ? "pt" : "mm");
	    }
	  else if (isdigit(*(q - 1)))
	    *--b = *--q;
	  else
	    break;
	}
      if (q != p)
	design_sizeF = ascii2float(b);
      if (verbose > 1) printf("# design size: %g%s\n", design_sizeF, unit);
  
      /*
       *  If the at-size list is non-empty, then construct a
       *  mag list from this list and the current design size:
       */
      if (atsize_list)
	{
	  struct float_node *t = mag_list = atsize_list;
	  atsize_list = (struct float_node *)0;
      
	  if (verbose) printf("# calculating mag(s) from at-size(s)...\n");

	  for (; t; t = t->next)
	    t->val = stepmag(t->val / design_sizeF);
	}
  
      extra = assoc_match_backwards(extra_alist, fn, &q);
      italic = assoc_match_backwards(italic_alist, fn, &q);
      cndnsd = assoc_match_backwards(cndnsd_alist, fn, &q);
      weight = assoc_match_backwards(weight_alist, fn, &q);

      /*
       *  Having parsed the <substyle> part, all that's left is
       *  the basic font name.  
       */
    }

  /* Canonical version of font name */
  if (q > fn && *(q - 1) == '-')
    q--;			/* drop hyphen at end of basename */
  for (b = canonical, p = fn; p < q; *b++ = *p++)
    ;  /* copy up to before q */
  if (*p == '-')
    p++;			/* skip hyphen */
  while (*b++ = *p++)
    ;				/* copy suffix + size parts including */
				/* final null */

  /*  Make copy of basename  */
  for (b = basename, p = fn; p < q; *b++ = *p++)
    ;
  *b = '\0';
  if (verbose > 1)
    printf("# full font name: \"%s\"\n# basic font name: \"%s\"\n",
	   canonical, basename);

  /*
   *  Now allow values set by suer to over-ride derived values:
   */
  if (ovr_weight) weight = ovr_weight;
  if (ovr_cndnsd) cndnsd = ovr_cndnsd;
  if (ovr_italic) italic = ovr_italic;
  if (ovr_extra) extra = ovr_extra;
  if (ovr_driver)
    {
      if (is_just_hyphen(ovr_driver))
        driver = (char *)NULL;	/* `-g -' cancels driver altogether */
      else
        driver = ovr_driver;
    }
  else
    driver = basename;
  if (ovr_pkpxl)
    pkpxl = ovr_pkpxl;
  else
    pkpxl = (pxlB ? "pxl" : "pk");

  for (pxl = pxlfactor_list; pxl; pxl = pxl->next)
    {
      if (pxlB && verbose > 2)
	printf("#   - factor: %g\n", pxl->val);
  
      for (mag = mag_list; mag; mag = mag->next)
	{
	  if (verbose > 2) printf("#     - mag: %g\n", mag->val);
	  for (dpi = dpi_list; dpi; dpi = dpi->next)
	    {
	      if (ovr_mode)
		do_one_font(canonical, ovr_mode, dpi->val, mag->val, pxl->val);
	      else
		{
		  char scratch[20];
		  sprintf(scratch, "%d", int_round(dpi->val));
		  mode = assoc(mode_alist, scratch);
		  if (verbose > 2)
		    printf("#       - mode_def: \"%s\"\n", mode);
		  do_one_font(canonical, mode, dpi->val, mag->val, pxl->val);
		}
	    }
	}
    }
}



/*  FUNCTIONS FOR PROCESSING COMMAND-LINE ARGUMENTS */

/*  Lookup table for variables changed by options  */

typedef union
{
  struct float_node *floats;
  struct eqn_node *eqns;
  char 	       *string;
  int		bool;
  Assoclist	alist;
} Param;

static Param *opt_obj[] =
{
  (Param *)&atsize_list,		/* -a ATSIZEs */
  0,
  (Param *)&ovr_cndnsd,		/* -c MF-EQN */
  (Param *)&dpi_list,		/* -d DPIs */
  (Param *)&eqns,		/* -e MF-EQN */
  0,				/* -f FILE */
  (Param *)&ovr_driver,		/* -g DRIVER */
  0,
  (Param *)&ovr_italic,		/* -i MF-EQN */
  (Param *)&ovr_extra,		/* -j MF-EQN */
  (Param *)&pxlfactor_list,		/* -k FACTORs */
  0, 
  (Param *)&mag_list,		/* -m MAGSTEPs */
  (Param *)&dryrun,		/* -n, +n */
  0,
  (Param *)&ovr_pkpxl,		/* -p PIXEL */
  0, 0,
  (Param *)&mag_list,		/* -s SCALED */
  0, 0,
  (Param *)&verbose,		/* -v, +v */
  (Param *)&ovr_weight,		/* -w MF-EQNS */
  (Param *)&pxlB,		/* -x, +x */
  0,
  (Param *)&ovr_mode,		/* -z MODE */
  0, 0,
  (Param *)&cndnsd_alist,	/* -C SUFFIX=MF-EQN */
  0,
  (Param *)&eqns2,		/* -E MF-EQN */
  0,
  (Param *)&gfto_template,	/* -G TEMPLATE */
  0,
  (Param *)&italic_alist,	/* -I SUFFIX=MF-EQN */
  (Param *)&extra_alist,	/* -J SUFFIX=MF-EQN */
  0, 0,
  (Param *)&mf_template,	/* -M TEMPLATE */
  0, 0,
  (Param *)&pk_template,	/* -P TEMPLATE */
  0,
  (Param *)&retain_gf,	/* -R */
  (Param *)&no_parse,		/* -S */
  (Param *)&tfm_template,	/* -T TEMPLATE */
  0, 0,
  (Param *)&weight_alist,	/* -W SUFFIX=MFEQN */
  0, 0,
  (Param *)&mode_alist		/* -Z DPI=MODE */  
};

#define PARAM(C)	\
      (opt_obj[isupper(C) ? uc_ord(C) + 26 : lc_ord(C)])

static char optstring[] = "a:C:c:Dd:e:E:f:G:g:hI:i:J:j:k:M:m:nP:p:R\
s:ST:VvW:w:xZ:z:?";
				/* enough to be getting on with... */

static Argloop_options opts;

static void
usage(c)
     int c;
{
  syntaxf("Don't understand flag -%c.\n\
USAGE:\t%s [ OPTION ... ] FONT ...\n\
\t%s -V | -h\n\
FONT is a TeX font name, such as \"cmbx12\".\n\
For a list of options, use `%s -h'",
	 c, progname, progname, progname, progname);
}

/*  before and after are brackets, first is format string for first element
 *  and rest is format string for each of the rest.
 *
 *  This has to be a macro, because it accesses the basetype of the list
 */

#define LIST_PRINT(T, ll, before, first, rest, after)\
   { T *l = ll; \
    fputs(before, stdout);\
    if (l)\
      {\
	printf(first, l->val);\
	for (l = l->next; l; l = l->next)\
	  printf(rest, l->val);\
      }\
    fputs(after, stdout);\
  } 

#define TEMPLATE_PRINT(S, T, U) \
  if (T) printf(S, T); else puts(U);

static void
do_flag(c, val)
     int	c;
     int	val;
{
  Param *p = PARAM(c);
  
  switch (c)
    {
    case '?':
    case 'h':
      printf( "%s [ OPTION ... FONT ] ... \n\
%s -V | -h\n\
\nFONT is a TeX font name, such as \"cmbx12\".\n\
\nOPTIONS\n\
  -V (version)  -h (help)  -D (dump tables)  -v (verbose)  -R (retain GF)\n\
  -x (pXl)  or +x (pk)  -k FACTOR(S)  -p PKSUFFIX\n\
  -a ATSIZE(S)  or -s SCALED(S)  or -m MAGSTEP(S)\n\
  -d DPI(S)       -e EQN	-E EQN\n\
\nTEMPLATES\n\
  -M METAFONT  -G GFtoPK  -T TFMdirectory  -P PKdirectory\n\
\t%%k = factor*1000  %%s = scaled*1000  %%p = pk or pxl\n\
\t%%d = dpi  %%D = scaled*dpi  %%n = scaled*factor*dpi\n\
\t%%z = mode  %%b = basename  %%g = driver  %%f = font\n\
\nTABLES\n\
  -W STRING=EQN  -C STRING=EQN  -I STRING=EQN  -J STRING=EQN  \
-Z DPI=MODENAME\n\
  -w EQN         -c EQN   	-i EQN	       -j EQN         -z MODENAME\n\
", progname, progname);
      break;
    case 'V':
      printf("%s\n", version);
      break;
    case 'D':
      printf("Z = "); assoc_print(mode_alist);
      if (ovr_mode) printf("overridden by \"%s\".\n", ovr_mode);
      printf("W = "); assoc_print(weight_alist);
      if (ovr_weight) printf("overridden by \"%s\".\n", ovr_weight);
      printf("C = "); assoc_print(cndnsd_alist);
      if (ovr_cndnsd) printf("overridden by \"%s\".\n", ovr_cndnsd);
      printf("I = "); assoc_print(italic_alist);
      if (ovr_italic) printf("overridden by \"%s\".\n", ovr_italic);
      printf("J = "); assoc_print(extra_alist);
      if (ovr_extra) printf("overridden by \"%s\".\n", ovr_extra);
      {
	int i;
	
	printf("template = {");
	for (i = 0; i < 52; i++)
	  if (table[i])
	    printf("\n\t%%%c\t%s",
		   (i < 26 ? i + 'a' : i + 'A' - 26), /* assumes ASCII */
		   table[i]);
	printf(" }\n");
      }
      
      if (eqns)
	LIST_PRINT(struct eqn_node, eqns, "-e  ", "%s", "; %s", ".\n");
      if (eqns2)
	LIST_PRINT(struct eqn_node, eqns2,"-E  ", "%s", "; %s", ".\n");
      LIST_PRINT(struct float_node, dpi_list,
		 "DPI(s)   = ", "%g", ", %g", ".\n");
      LIST_PRINT(struct float_node, mag_list,
		 "mag(s)   = ", "%g", ", %g", ".\n");
      if (atsize_list)
	LIST_PRINT(struct float_node, atsize_list,
		   "atsize(s)= ", "%g", ", %g", ".\n");
      if (pxlB)
	LIST_PRINT(struct float_node, pxlfactor_list,
		   "factor(s)= ", "%g", ", %g", ".\n");
      TEMPLATE_PRINT("METAFONT = \"%s\"\n", mf_template, "don't METAFONT");
      TEMPLATE_PRINT("GFtoPK   = \"%s\"\n", gfto_template, "don't GFtoPK");
      TEMPLATE_PRINT("TFM dir. = \"%s\"\n", tfm_template, "no TFM directory");
      TEMPLATE_PRINT("PK dir.  = \"%s\"\n", pk_template, "no PK directory");
      break;

    case 'n': case 'x': case 'R': case 'S':
      p->bool = val; break;

    case 'v':			/* -vv is more verbose than -v */
      if (val) verbose++; else verbose = 0;
      break;
      
      /*
       *  Set the default value for certain flagged arguments:
       *  (+F type flags)
       */
    case 'd':
      DISPOSE(dpi_list);
      dpi_list = NEW(struct float_node);
      dpi_list->next = (struct float_node *)0; 
      dpi_list->val = 200.0;	/* only one defined in standard plain.mf */
      break;

    case 'Z': case 'W': case 'C': case 'I': case 'J':	
      p->alist = assoc_destroylist(p->alist);
      if (c == 'Z')
	{
	  mode_alist = assoc_add(mode_alist, strdup("200=lowres"));
	}
      break;
      
    case 'z': case 'w': case 'c': case 'i': case 'j':
    case 'p':
    case 'g':
    case 'M': case 'G': case 'T': case 'P': 
      if (p->string) xfree(p->string);
      switch (c)
	{
	case 'M': mf_template = strdup("mf"); break; 
	case 'G': gfto_template = strdup("gfto%p"); break; 
	case 'T': tfm_template = strdup("%f.tfm"); break; 
	case 'P': pk_template = strdup("%f.%n%p"); break; 
	default:
	  p->string = (char *)NULL; break;
	}
      break;
      
    case 'e': case 'E':
      FREE_DISPOSE(p->eqns);
      break;
      
    case 'k':
      DISPOSE(pxlfactor_list);
      pxlfactor_list = NEW(struct float_node);
      pxlfactor_list->next = (struct float_node *)0; 
      pxlfactor_list->val = 5.0;
      break;

    case 'a': case 's': case 'm':
      DISPOSE(mag_list); DISPOSE(atsize_list);
      mag_list = NEW(struct float_node);
      mag_list->next = (struct float_node *)0; 
      mag_list->val = 1.0;
      break;

    default:
      syntaxf("Unexpected %c%c!", (val ? '-' : '+'), c);
    }
}


/*
 *  This func is used below when changing comma-separated list into
 *  a float_node list.
 */

static inline void
build_list(p, s, f)
     register struct float_node **p;
     const char *s;
     register double (*f) ARGS((const char *));
{
      struct float_node *cur = 0; 
      char *state, *t;
      DISPOSE(*p);
      s = stritem(t = strdup(s), ',', &state); /* s is now 1st item */
      if (!s) errorf("warning: \"%s\" denotes the empty list", t);
      for (; s; s = stritem((char *)NULL, ',', &state))
	{ 
	  if (cur) 
	    cur = cur->next = NEW(struct float_node); 
	  else 
	    cur = *p = NEW(struct float_node); 
	  cur->val = f(s); 
	} 
      if (cur) cur->next = 0; 
      xfree((char *)t); 
}

static double 
ascii2float(s)				/* this version complains if invalid */
     const char *s;
{
  const char *t = s;
  double f = 0.0, denom = 10;
  register int i = 0;
  
  if (!s || !*s) syntaxf("the empty string is not a valid number");
  if (*s == '-') return -ascii2float(s + 1);
  
  /*  This bit done in integer arithmetic for efficiency's sake:  */
  while (*s && isdigit(*s))
    i = 10 * i + (*s++ - '0');
  f = i;
  if (*s == '.')
    while (*++s && isdigit(*s))
      f += (*s - '0')/denom, denom *= 10.0;
  if (*s) syntaxf("%s: invalid number", t);
  return f;
}

static double 
ascii2scale(s)
     const char *s;
{
  register float f = ascii2float(s);	/* aborts if invalid */
  
  if (f <= 0.0)
    syntaxf("%g: invalid scale (must be positive floating-point number)", f);
  if (f > 100.0) return f/1000.0;
  return f;
}

static double
ascii2magstep(s)
     const char *s;
{
  if (*s == '-')
    return 1/ascii2magstep(s + 1);
  if (*s == 'h' && !s[1])
    return MAGSTEPHALF;
  if (!isdigit(*s) || s[1])
    syntaxf("%s: invalid magstep (must be h, -h or small integer)", s);
  return magstep(*s - '0');
}

static void
do_arg(c, s)			/* Process one flagged argument */
     int c;			/* flag character */
     const char *s;		/* argument */
{
  Param *p = PARAM(c);
  
  switch (c)
    {

      /*
       *  Options that store a string allow the special argument
       *  `-' to set its value to (char *)NULL, meaning don't use it:
       */
    case 'p':
    case 'z': case 'w': case 'c': case 'i': 
    case 'j':			/* - pdc Mon 16 Jul 1990 */
    case 'M': case 'G': case 'T': case 'P':
      if (p->string)
	xfree(p->string);
      p->string
	= (!s || is_just_hyphen(s) ? (char *)NULL : strdup(s));
      break;

      /*
       *  ... except -g, which has to distinguish between NO driver
       *  file (-g -, ovr_driver = "-"), DEFAULT driver file (+g,
       *  ovr_driver = null) and SPECIAL driver file (-g string):
       */
    case 'g':
      if (ovr_driver) xfree(ovr_driver);
      ovr_driver = strdup(s);	/* ...even if s is just a hyphen */
      break;      

    case 'd': case 'a':
      build_list(&p->floats, s, ascii2float);
      break;

    case 's':
      DISPOSE(atsize_list);
      /* FALL THROUGH */
    case 'k':
      build_list(&p->floats, s, ascii2scale);
      break;
      
    case 'm':
      DISPOSE(atsize_list);
      build_list(&p->floats, s, ascii2magstep);
      break;
      
      /*  Add items to an alist: */
    case 'Z': case 'W': case 'C': case 'I':
    case 'J':	
      if (!s) syntaxf("expected argument after -%c", c);
      p->alist = assoc_add(p->alist, strdup(s));
      break;

      /*  Add strings to a list:  */
    case 'e': case 'E':
      if (!s || is_just_hyphen(s))
	FREE_DISPOSE(p->eqns);
      else	  
	{
	  struct eqn_node *new = NEW(struct eqn_node);
	  new->val = strdup(s);
	  new->next = p->eqns;
	  p->eqns = new;
	}
      break;

    case 'f':			/* read from file of commands */
      {
	char scratch[MAXPATHLEN];

	if (!s || is_just_hyphen(s))
	  {
	    clearerr(stdin);
	    argloop(al_file(stdin), &opts);
	  }
	else if (!findfile(scratch, s, mffpath, suffix))
	  pfatalf("can't find command file \"%s\"", s);
	else
	  argloop(al_file(fopen(scratch, "r")), &opts);
      }
      break;

    case '\0': 
      do_font(s);
      break;

    default:
      syntaxf("Unexpected -%c %s!", c, s);
    }
}

int
main(argc, argv)
     int	argc;
     const char **argv;
{
  const char   *getenv();
  char	       *t;

#ifdef FANCY_MALLOC
  mallopt(M_MXFAST, 32);	/* blocks up to 32 bytes allocated in batches */
#endif

  if (!(mffpath  = getenv("MFFPATH")))  mffpath = ".";

  opts.optstring = optstring;
  opts.flag = do_flag;
  opts.arg = do_arg;
  opts.error = usage;
  
  argloop(al_string(t = strdup("+ZMGTPxdks")), &opts);
  xfree(t);			/* Set the default value for all except */
				/* things must have NULL as default */

#ifdef NO_ARGV0
  argloop_init_files("mff", &opts);
#else
  argloop_init_files(argv[0], &opts);
#endif
  argloop(al_argv(argc, argv), &opts);

  return 0;
}
