/*
 * Copyright (c) 1987 University of Maryland Department of Computer Science.
 * All rights reserved.  Permission to copy for any purpose is hereby granted
 * so long as this copyright notice remains intact.
 */

#ifndef lint
static char rcsid[] = "$Header: font.c,v 2.6 87/06/16 18:27:56 chris Exp $";
#endif

/*
 * Routines for working with fonts.  In particular, the configuration
 * dependent code is here.
 *
 * Specific fonts (GF, PXL, etc.) have functions in separate files.
 */

#include <stdio.h>
#include <errno.h>
#include "types.h"
#include "conv.h"
#include "font.h"

/*
 * Define the default configuration file.
 * Also define the maximum path name length.
 */
#ifndef CONFFILE
#define CONFFILE "/usr/local/lib/tex/fontdesc"
#endif

#define	PATHLEN	1024

/*
 * A font configuration.  The font list is ordered.
 *
 * A specifier is typically a particular print engine, since
 * different engines need slightly different fonts.
 */
struct fontconf {
	struct	fontconf *fc_next;
	struct	fontops *fc_ops;
	char	*fc_path;	/* path, with metacharacters */
	char	*fc_spec;	/* specifier */
	int	fc_slop;	/* slop value */
};

/*
 * EQ is a fast way to check for string equivalence.
 */
#define	EQ(a, b) (*(a) == *(b) && strcmp(a, b) == 0)

/*
 * Private variables.
 */
static	int didinit;		/* true => initialised already */
static	char *cfname;		/* config file name, for errors */
static	int cfline;		/* config file line, likewise */
static	struct fontops *fontops;/* font operations code: list head */
static	struct fontconf *fonts;	/* font list */
static	struct fontconf **nextfc;/* used during initialisation */
static	char spec_any[] = "*";	/* the `anything' specifier */

/*
 * Imports.
 */
extern	int errno;
char	*getenv(), *malloc(), *strsave();
#ifndef sys5
char	*sprintf();
#endif

/*
 * Here, alas, we know about all the kinds of fonts.
 * This also means that every DVI interpreter pulls in
 * the full set of font manipulation routines.
 *
 * PERHAPS THIS SHOULD BE CONFIGURABLE.
 */
#define	ADDFONT(x) { \
	extern struct fontops x; \
	x.fo_next = fontops; \
	fontops = &x; \
}

fontinit(file)
	char *file;
{

	if (didinit) {
		/*
		 * Could free the old configuration and fire up
		 * a new one, but for now . . .
		 */
		error(1, 0, "attempt to reinit fonts");
		/* NOTREACHED */
	}
	didinit++;
	ADDFONT(boxops);
	ADDFONT(blankops);
	ADDFONT(invisops);
	ADDFONT(pxlops);
	ADDFONT(pkops);
	ADDFONT(gfops);
	nextfc = &fonts;
	if (file == NULL)
		if ((file = getenv(CONFENV)) == NULL)
			file = CONFFILE;
	readconf(file);
}

/*
 * A proto resembles a fontspec (indeed, it is a prototype
 * fontspec) but is not quite the same.  It is used to gather
 * the information needed per fontspec before allocating
 * the fontspec itself.
 */
struct proto {
	char	*p_type;
	char	*p_spec;
	char	*p_slop;
	char	*p_path;
};

/*
 * Read the named configuration file.  The file is split into
 * lines, and lines are split into words; if the first word is
 * "font", this is a fontconf, and we read the remainder of the
 * words and make a fontconf entry.
 */
static
readconf(name)
	char *name;
{
	register FILE *f;	/* config file */
	register char **p;	/* pointer into word vector */
	register int c;
	char line[1024];	/* input line */
	char *v[100];		/* word vector */
	struct proto proto;	/* prototype fontconf */

#define GETWORD(x, ifnone) \
	if (--c <= 0) \
		badcf(ifnone); \
	else \
		(x) = *p++

	if ((f = fopen(name, "r")) == NULL)
		error(1, errno, "cannot read font configuration file \"\%s\"",
			name);
	cfname = name;
	cfline = 0;
	while (fgets(line, sizeof (line), f) != NULL) {
		cfline++;
		if ((c = strlen(line)) > 0) {
			if (line[--c] != '\n')
				badcf("line too long");
			line[c] = 0;
		}
		if ((c = split(line, v, sizeof (v) / sizeof (*v))) < 0)
			badcf("too many words");
		p = v;
		/* skip things that are not fonts */
		if (c == 0 || !EQ(*p, "font"))
			continue;
		p++;
		GETWORD(proto.p_type, "missing font typename");
		GETWORD(proto.p_spec, "missing font spec (engine)");
		GETWORD(proto.p_slop, "missing slop value");
		GETWORD(proto.p_path, "need pathname");
		(void) setfont(&proto);
	}
}

/*
 * Find a font's operations, given its name.
 */
static struct fontops *
findops(name)
	register char *name;
{
	register struct fontops *fo;

	for (fo = fontops; fo != NULL; fo = fo->fo_next)
		if (EQ(fo->fo_name, name))
			return (fo);
	return (NULL);
}

/*
 * Turn a prototype fontconf into a real one.
 */
static int
setfont(p)
	register struct proto *p;
{
	register struct fontconf *fc;
	struct fontops *ops = findops(p->p_type);

	if (ops == NULL) {
		error(0, 0,
			"\"%s\", line %d: unknown font type \"%s\" ignored",
			cfname, cfline, p->p_type);
		return (-1);
	}
	if ((fc = (struct fontconf *) malloc(sizeof (*fc))) == NULL)
		error(1, errno,
			"out of memory for font configuration (sorry)");
	fc->fc_ops = ops;
	fc->fc_next = NULL;
	fc->fc_path = strsave(p->p_path);
	fc->fc_spec = EQ(p->p_spec, spec_any) ? NULL : strsave(p->p_spec);
	fc->fc_slop = atoi(p->p_slop);
	if (fc->fc_slop < 1)	/* quietly enforce proper slops */
		fc->fc_slop = 1;
	*nextfc = fc;
	nextfc = &fc->fc_next;
	return (0);
}

/*
 * Complain about a problem in the configuration file.
 */
static
badcf(why)
	char *why;
{

	error(1, 0, "\"%s\", line %d: %s", cfname, cfline, why);
	/* NOTREACHED */
}

/*
 * Turn a prototype path, name, and magnification into a full
 * path.
 */
static
pave(result, proto, name, mag)
	char *result, *proto, *name;
	int mag;
{
	register int c;
	register char *s, *d, *p;
	char num[30];

	d = result;
	p = proto;
	s = NULL;
	num[0] = 0;		/* will need changing for other bases */

	while (p != NULL) {
		/*
		 * If sourcing from s, take its next character, and
		 * insert it directly.  Otherwise take the next path
		 * character and interpret it.
		 */
		if (s != NULL) {
			if ((c = *s++) == 0) {
				s = NULL;
				continue;
			}
			goto put;
		}
		if ((c = *p++) == 0)
			p = NULL;
		if (c != '%')
			goto put;

		switch (c = *p++) {

		case 'f':
		case 'n':
		case 's':
			s = name;
			continue;

		case 'd':
		case 'm':
			if (num[0] == 0)
				(void) sprintf(num, "%d", mag);
			s = num;
			continue;

		case 0:
			c = '%';
			p--;
			/* FALLTHROUGH */
		}
put:
		if (d - result >= PATHLEN)
			error(1, 0, "font path `%s' too long (sorry)", proto);
		*d++ = c;
	}
}

struct font *getafont();	/* get a font and optional rasters */

/*
 * Given a font name and size, return the first font that fits, along
 * with its name (via fname).  If we cannot find such a font, we set
 * *fname to point to a `canonical' example font name, unless there are
 * are no fonts for the device, in which case we set *fname to NULL.
 */
struct font *
GetFont(nm, dvimag, dvidsz, dev, fname)
	char *nm;
	i32 dvimag, dvidsz;
	char *dev, **fname;
{

	return (getafont(nm, dvimag, dvidsz, dev, fname, 1));
}

/*
 * Same as GetFont, but caller promises never to ask for rasters.
 */
struct font *
GetRasterlessFont(nm, dvimag, dvidsz, dev, fname)
	char *nm;
	i32 dvimag, dvidsz;
	char *dev, **fname;
{

	return (getafont(nm, dvimag, dvidsz, dev, fname, 0));
}

/*
 * NEED TO THINK ABOUT gf NAMING CONVENTIONS HERE: ARE THEY LIKE pxl?
 * WHAT ABOUT OTHERS?
 */
static struct font *
getafont(nm, dvimag, dvidsz, dev, fname, wantrast)
	char *nm;
	i32 dvimag, dvidsz;
	char *dev, **fname;
	int wantrast;
{
	register int slop, fmag;
	register struct font *f;
	register struct fontconf *fc;
	register char *path;
	static char firstpath[PATHLEN], laterpath[PATHLEN];
	double mag;
	int scaled;

	if (!didinit)
		fontinit((char *) NULL);

	/*
	 * The equation below means, approximately, `the font is
	 * magnified by the ratio of the actual size dvimag to the
	 * design size dvidsz, and then further scaled by the
	 * global magnification.'  We multiply this by the printer's
	 * resolution in dots per inch, then use the per-font
	 * conversion factor to convert a dots-per-inch value to
	 * a font name `%m' magnification (extension).
	 */
	mag = (double) dvimag / (double) dvidsz;
	scaled = mag * 1000.0 + 0.5;
	mag *= Conversion.c_mag * Conversion.c_dpi;

	path = firstpath;
	for (fc = fonts; fc != NULL; fc = fc->fc_next) {
		if (dev != NULL && fc->fc_spec != NULL &&
		    !EQ(dev, fc->fc_spec))
			continue;
		fmag = mag * fc->fc_ops->fo_dpitomag + 0.5;
		for (slop = 0; slop < fc->fc_slop; slop++) {
			pave(path, fc->fc_path, nm, fmag + slop);
			if (access(path, 4) == 0)
				goto found;
			if (slop) {
				pave(path, fc->fc_path, nm, fmag - slop);
				if (access(path, 4) == 0)
					goto found;
			}
			path = laterpath;
		}
	}

	/* not found */
	if (path == firstpath) {	/* never got to try any paths */
		*fname = NULL;
		errno = ENXIO;
	} else {
		*fname = firstpath;
		errno = ENOENT;
	}
	return (NULL);

found:
	*fname = path;

	/* allocate space for the per-font info, and read it in */
	f = (struct font *) malloc(sizeof (struct font));
	if (f == NULL)
		return (NULL);
	f->f_flags = wantrast ? FF_RASTERS : 0;
	f->f_ops = fc->fc_ops;
	f->f_path = strsave(path);
	f->f_font = strsave(nm);
	f->f_dvimag = dvimag;
	f->f_dvidsz = dvidsz;
	f->f_scaled = scaled;
	f->f_checksum = 0;	/* in case the font reader cannot get one */
	errno = 0;
	if ((*f->f_ops->fo_read)(f)) {
		int e = errno;	/* paranoid */

		free(f->f_path);
		free(f->f_font);
		free((char *) f);
		errno = e;
		return (NULL);
	}
	return (f);
}
