/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


/*	from OpenSolaris "t6.c	1.9	05/06/08 SMI"	*/

/*
 * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany
 *
 * Sccsid @(#)t6.c	1.194 (gritter) 2/7/10
 */

/*
 * Changes Copyright (c) 2014 Carsten Kunze <carsten.kunze at arcor.de>
 */

/*
 * Portions Copyright (c) 2017 Roy Fisher
 */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*
 * t6.c
 *
 * width functions, sizes and fonts
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "tdef.h"
#include "dev.h"
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "ext.h"
#include "afm.h"
#include "pt.h"
#include "troff.h"
#include "fontmap.h"

int ps2cc(const char *name);

/* fitab[f][c] is 0 if c is not on font f */
	/* if it's non-zero, c is in fontab[f] at position
	 * fitab[f][c].
	 */
int	*fontlab;
int	*pstab;
int	*cstab;
int	*ccstab;
int	**fallbacktab;
float	*zoomtab;
int	*bdtab;
int	**lhangtab;
int	**rhangtab;
int	**kernafter;
int	**kernbefore;
int	**ftrtab;
struct lgtab	**lgtab;
int	***lgrevtab;
struct tracktab	*tracktab;
static int	sbold = 0;
int	kern = 0;
struct box	mediasize, bleedat, trimat, cropat;
int	psmaxcode;
struct ref	*anchors, *links, *ulinks;
static int	_minflg;
int	lastrst;
int	lastrsb;
static int	spacewidth;

static void	kernsingle(int **);
static int	_ps2cc(const char *name, int create);

int
width(register tchar j)
{
	register int i, k;

	_minflg = minflg;
	minflg = minspc = 0;
	lasttrack = 0;
	rawwidth = 0;
	lastrst = lastrsb = 0;
	if (isadjspc(j))
		return(0);
	if (j & (ZBIT|MOT)) {
		if (iszbit(j))
			return(0);
		if (isvmot(j))
			return(0);
		k = absmot(j);
		if (isnmot(j))
			k = -k;
		return(k);
	}
	i = cbits(j);
	if (html && i >= NCHARS)
		i = ' ';
	if (i < ' ') {
		if (i == '\b')
			return(-widthp);
		if (i == PRESC)
			i = eschar;
		else if (iscontrol(i))
			return(0);
		else if (isxfunc(j, CHAR)) {
			k = charout[sbits(j)].width + lettrack;
			lastrst = charout[sbits(j)].height;
			lastrsb = -charout[sbits(j)].depth;
			goto set;
		}
	} else if (i == ' ' && issentsp(j))
	{
		_minflg = 0;
		return(ses);
	}
	if (i==ohc)
		return(0);
	if (!xflag || !isdi(j)) {
		if (i == STRETCH)
			setcbits(j, ' ');
		i = trtab[i];
		i = ftrans(fbits(j), i);
	}
	if (i < 32)
		return(0);
	lasttrack = 0;
	if (sfbits(j) == oldbits) {
		xfont = pfont;
		xpts = ppts;
	} else
		xbits(j, 0);
	if (widcache[i-32].fontpts == xfont + (xpts<<8) &&
			(i > 32 || widcache[i-32].evid == evname) &&
			!setwdf && !_minflg && !horscale) {
		rawwidth = widcache[i-32].width - widcache[i-32].track;
		k = widcache[i-32].width + lettrack;
		lastrst = widcache[i-32].rst;
		lastrsb = widcache[i-32].rsb;
		lasttrack = widcache[i-32].track;
	} else {
		if (_minflg && i == 32 && cbits(j) != 32)
			_minflg = 0;
		k = getcw(i-32);
		if (i == 32 && _minflg && !cs) {
			_minflg = 0;
			minspc = getcw(0) - k;
		}
		_minflg = 0;
	set:	if (bd && !fmtchar)
			k += (bd - 1) * HOR;
		if (cs && !fmtchar)
			k = cs;
	}
	widthp = k;
	return(k);
}

/*
 * clear width cache-- s means just space
 */
void
zapwcache(int s)
{
	register int i;

	if (s) {
		widcache[0].fontpts = 0;
		return;
	}
	for (i=0; i<NWIDCACHE; i++)
		widcache[i].fontpts = 0;
}

int
getcw(register int i)
{
	register int	k;
	register int	*p;
	register int	x, j;
	int nocache = 0;
	int	ofont = xfont;
	int	s, t;
	double	z = 1, zv;
	struct afmtab	*a;
	int	cd = 0;

	bd = 0;
	if (i >= nchtab + 128-32) {
		if (afmtab && fontbase[xfont]->afmpos - 1 >= 0) {
			cd = nchtab + 128;
			i -= cd;
		} else {
			j = abscw(i + 32 - (nchtab+128));
			goto g0;
		}
	}
	if (i == 0) {	/* a blank */
		if (_minflg) {
			j = minspsz;
			nocache = 1;
		} else
			j = spacesz;
		if (fontbase[xfont]->cspacewidth >= 0)
			k = fontbase[xfont]->cspacewidth;
		else if (spacewidth || gflag)
			k = fontbase[xfont]->spacewidth;
		else
			k = fontab[xfont][0];
		k = (k * j + 6) / 12;
		lastrst = lastrsb = 0;
		/* this nonsense because .ss cmd uses 1/36 em as its units */
		/* and default is 12 */
		goto g1;
	}
	if ((j = fitab[xfont][i]) == 0) {	/* it's not on current font */
		int ii, jj, _t;
		/* search through search list of xfont
		 * to see what font it ought to be on.
		 * first searches explicit fallbacks, then
		 * searches S, then remaining fonts in wraparound order.
		 */
		nocache = 1;
		if (fallbacktab[xfont]) {
			for (jj = 0; fallbacktab[xfont][jj] != 0; jj++) {
				if ((ii = findft(fallbacktab[xfont][jj],0)) < 0)
					continue;
				_t = ftrans(ii, i + 32) - 32;
				j = fitab[ii][_t];
				if (j != 0) {
					xfont = ii;
					goto found;
				}
			}
		}
		if (smnt) {
			for (ii=smnt, jj=0; jj < nfonts; jj++, ii=ii % nfonts + 1) {
				if (fontbase[ii] == NULL)
					continue;
				_t = ftrans(ii, i + 32) - 32;
				j = fitab[ii][_t];
				if (j != 0) {
					/*
					 * troff traditionally relies on the
					 * device postprocessor to find the
					 * appropriate character since it
					 * searches the fonts in the same
					 * order. This does not work with the
					 * new requests anymore, so change
					 * the font explicitly.
					 */
					if (xflag)
						xfont = ii;
				found:	p = fontab[ii];
					k = *(p + j);
					if (afmtab &&
					    (_t=fontbase[ii]->afmpos-1)>=0) {
						a = afmtab[_t];
						if (a->bbtab[j]) {
							lastrst = a->bbtab[j][3];
							lastrsb = a->bbtab[j][1];
						} else {
							lastrst = a->ascender;
							lastrsb = a->descender;
						}
					}
					if (xfont == sbold)
						bd = bdtab[ii];
					if (setwdf)
						numtab[CT].val |= kerntab[ii][j];
					goto g1;
				}
			}
		}
		k = fontab[xfont][0];	/* leave a space-size space */
		lastrst = lastrsb = 0;
		goto g1;
	}
 g0:
	p = fontab[xfont];
	if (setwdf)
		numtab[CT].val |= kerntab[xfont][j];
	if (afmtab && (t = fontbase[xfont]->afmpos-1) >= 0) {
		a = afmtab[t];
		if (a->bbtab[j]) {
			lastrst = a->bbtab[j][3];
			lastrsb = a->bbtab[j][1];
		} else {
			/*
			 * Avoid zero values by all means. In many use
			 * cases, endless loops will result unless values
			 * are non-zero.
			 */
			lastrst = a->ascender;
			lastrsb = a->descender;
		}
	}
	k = *(p + j);
	if (dev.anysize == 0 || xflag == 0 || (z = zoomtab[xfont]) == 0)
		z = 1;
 g1:
	zv = z;
	if (horscale) {
		z *= horscale;
		nocache = 1;
	}
	if (!bd)
		bd = bdtab[ofont];
	if ((cs = cstab[ofont])) {
		nocache = 1;
		if ((ccs = ccstab[ofont]))
			x = pts2u(ccs);
		else
			x = xpts;
		cs = (cs * EMPTS(x)) / 36;
	}
	k = (k * z * u2pts(xpts) + (Unitwidth / 2)) / Unitwidth;
	lastrst = (lastrst * zv * u2pts(xpts) + (Unitwidth / 2)) / Unitwidth;
	lastrsb = (lastrsb * zv * u2pts(xpts) + (Unitwidth / 2)) / Unitwidth;
	rawwidth = k;
	s = xpts;
	lasttrack = 0;
	if (s <= tracktab[ofont].s1 && tracktab[ofont].n1)
		lasttrack = tracktab[ofont].n1;
	else if (s >= tracktab[ofont].s2 && tracktab[ofont].n2)
		lasttrack = tracktab[ofont].n2;
	else if (s > tracktab[ofont].s1 && s < tracktab[ofont].s2) {
		int	r;
		r = (s * tracktab[ofont].n2 - s * tracktab[ofont].n1
				+ tracktab[ofont].s2 * tracktab[ofont].n1
				- tracktab[ofont].s1 * tracktab[ofont].n2)
			/ (tracktab[ofont].s2 - tracktab[ofont].s1);
		if (r != 0)
			lasttrack = r;
	}
	k += lasttrack + lettrack;
	i += cd;
	if (nocache|bd)
		widcache[i].fontpts = 0;
	else {
		widcache[i].fontpts = xfont + (xpts<<8);
		widcache[i].width = k - lettrack;
		widcache[i].rst = lastrst;
		widcache[i].rsb = lastrsb;
		widcache[i].track = lasttrack;
		widcache[i].evid = evname;
	}
	return(k);
	/* Unitwidth is Units/Point, where
	 * Units is the fundamental digitization
	 * of the character set widths, and
	 * Point is the number of goobies in a point
	 * e.g., for cat, Units=36, Point=6, so Unitwidth=36/6=6
	 * In effect, it's the size at which the widths
	 * translate directly into units.
	 */
}

int
abscw(int n)	/* return index of abs char n in fontab[], etc. */
{	register int i, ncf;

	ncf = fontbase[xfont]->nwfont & BYTEMASK;
	for (i = 0; i < ncf; i++)
		if (codetab[xfont][i] == n)
			return i;
	return 0;
}

int
onfont(tchar c)
{
	int	k = cbits(c);
	int	f = fbits(c);

	if (k <= ' ')
		return 1;
	k -= 32;
	if (k >= nchtab + 128-32) {
		if (afmtab && fontbase[f]->afmpos - 1 >= 0)
			k -= nchtab + 128;
		else
			return abscw(k + 32 - (nchtab+128)) != 0;
	}
	return fitab[f][k] != 0;
}

static int
fvert2pts(int f, int s, int k)
{
	double	z;

	if (k != 0) {
		k = (k * u2pts(s) + (Unitwidth / 2)) / Unitwidth;
		if (dev.anysize && xflag && (z = zoomtab[f]) != 0)
			k *= z;
	}
	return k;
}

int
getascender(void)
{
	struct afmtab	*a;
	int	n;

	if ((n = fontbase[font]->afmpos - 1) >= 0) {
		a = afmtab[n];
		return fvert2pts(font, pts, a->ascender);
	} else
		return 0;
}

int
getdescender(void)
{
	struct afmtab	*a;
	int	n;

	if ((n = fontbase[font]->afmpos - 1) >= 0) {
		a = afmtab[n];
		return fvert2pts(font, pts, a->descender);
	} else
		return 0;
}

int
kernadjust(tchar c, tchar e)
{
	lastkern = 0;
	if (!kern || ismot(c) || ismot(e) || html || iszbit(c) || iszbit(e))
		return 0;
	if (!isdi(c)) {
		c = trtab[cbits(c)] | (c & SFMASK);
		c = ftrans(fbits(c), cbits(c)) | (c & SFMASK);
	}
	if (!isdi(e)) {
		e = trtab[cbits(e)] | (e & SFMASK);
		e = ftrans(fbits(e), cbits(e)) | (e & SFMASK);
	}
	return getkw(c, e);
}

#define	kprime		1021
#define	khash(c, d)	(((2654435769U * (c) * (d) >> 16)&0x7fffffff) % kprime)

static struct knode {
	struct knode	*next;
	tchar	c;
	tchar	d;
	int	n;
} **ktable;

static void
kadd(tchar c, tchar e, int n)
{
	struct knode	*kn;
	int	h;

	if (ktable == NULL)
		ktable = calloc(kprime, sizeof *ktable);
	h = khash(c, e);
	kn = calloc(1, sizeof *kn);
	kn->c = c;
	kn->d = e;
	kn->n = n;
	kn->next = ktable[h];
	ktable[h] = kn;
}

static void
kzap(int f)
{
	struct knode	*kp;
	int	i;

	if (ktable == NULL)
		return;
	for (i = 0; i < kprime; i++)
		for (kp = ktable[i]; kp; kp = kp->next)
			if (fbits(kp->c) == f || fbits(kp->d) == f)
				kp->n = INT_MAX;
}

static tchar
findchar(tchar c)
{
	int	f, i;

	f = fbits(c);
	c = cbits(c);
	i = c - 32;
	if (c != ' ' && i > 0 && i < nchtab + 128 - 32 && fitab[f][i] == 0) {
		int	ii, jj;
		if (fallbacktab[f]) {
			for (jj = 0; fallbacktab[f][jj] != 0; jj++) {
				if ((ii = findft(fallbacktab[f][jj], 0)) < 0)
					continue;
				if (fitab[ii][i] != 0) {
					f = ii;
					goto found;
				}
			}
		}
		if (smnt) {
			for (ii=smnt, jj=0; jj < nfonts; jj++, ii=ii % nfonts + 1) {
				if (fontbase[ii] == NULL)
					continue;
				if (fitab[ii][i] != 0) {
					f = ii;
					goto found;
				}
			}
		}
		return 0;
	}
found:
	setfbits(c, f);
	return c;
}

static struct knode *
klook(tchar c, tchar e)
{
	struct knode	*kp;
	int	h;

	c = findchar(c);
	e = findchar(e);
	h = khash(c, e);
	for (kp = ktable[h]; kp; kp = kp->next)
		if (kp->c == c && kp->d == e)
			break;
	return kp && kp->n != INT_MAX ? kp : NULL;
}

int
getkw(tchar c, tchar e)
{
	struct knode	*kp;
	struct afmtab	*a;
	int	f, g, i, j, k, n, s, I, J;
	double	z;

	if (isxfunc(c, CHAR))
		c = charout[sbits(c)].ch;
	if (isxfunc(e, CHAR))
		e = charout[sbits(e)].ch;
	lastkern = 0;
	if (!kern || iszbit(c) || iszbit(e) || ismot(c) || ismot(e))
		return 0;
	if (sbits(c) != sbits(e))
		return 0;
	f = fbits(c);
	g = fbits(e);
	if ((s = sbits(c)) == 0) {
		s = xpts;
		if (f == 0)
			f = xfont;
	}
	i = cbits(c);
	j = cbits(e);
	if (i == SLANT || j == SLANT || i == XFUNC || j == XFUNC || cstab[f])
		return 0;
	k = 0;
	if (i >= 32 && j >= 32) {
		if (ktable != NULL && (kp = klook(c, e)) != NULL)
			k = kp->n;
		else if ((n = (fontbase[f]->afmpos)-1) >= 0 &&
				n == (fontbase[g]->afmpos)-1 &&
				fontbase[f]->kernfont >= 0) {
			a = afmtab[n];
			I = i - 32;
			J = j - 32;
			if (I >= nchtab + 128)
				I -= nchtab + 128;
			if (J >= nchtab + 128)
				J -= nchtab + 128;
			k = afmgetkern(a, I, J);
			if (abs(k) < fontbase[f]->kernfont)
				k = 0;
		}
		if (j>32 && kernafter != NULL && kernafter[fbits(c)] != NULL)
			k += kernafter[fbits(c)][i];
		if (i>32 && kernbefore != NULL && kernbefore[fbits(e)] != NULL)
			k += kernbefore[fbits(e)][j];
	}
	if (k != 0) {
		k = (k * u2pts(s) + (Unitwidth / 2)) / Unitwidth;
		if (dev.anysize && xflag && (z = zoomtab[f]) != 0)
			k *= z;
	}
	lastkern = k;
	return k;
}

void
xbits(register tchar i, int bitf)
{
	register int k;

	xfont = fbits(i);
	k = sbits(i);
	if (k) {
		xpts = dev.anysize && xflag ? k : pstab[--k];
		oldbits = sfbits(i);
		pfont = xfont;
		ppts = xpts;
		return;
	}
	switch (bitf) {
	case 0:
		xfont = font;
		xpts = pts;
		break;
	case 1:
		xfont = pfont;
		xpts = ppts;
		break;
	case 2:
		xfont = mfont;
		xpts = mpts;
	}
}

static tchar
postchar1(const char *temp, int f)
{
	struct namecache	*np;
	struct afmtab	*a;
	int	i;

	if (afmtab && (i = (fontbase[f]->afmpos) - 1) >= 0) {
		a = afmtab[i];
		np = afmnamelook(a, temp);
		if (np->afpos != 0) {
			if (np->fival[0] != NOCODE &&
					fitab[f][np->fival[0]])
				return np->fival[0] + 32 + nchtab + 128;
			else if (np->fival[1] != NOCODE &&
					fitab[f][np->fival[1]])
				return np->fival[1] + 32 + nchtab + 128;
			else
				return 0;
		}
	}
	return(0);
}

static tchar
postchar(const char *temp, int *fp)
{
	int	i, j;
	tchar	c;

	*fp = font;
	if ((c = postchar1(temp, *fp)) != 0)
		return c;
	if (fallbacktab[font]) {
		for (j = 0; fallbacktab[font][j] != 0; j++) {
			if ((i = findft(fallbacktab[font][j], 0)) < 0)
				continue;
			if ((c = postchar1(temp, i)) != 0 && fchartab[c] == 0) {
				*fp = i;
				return c;
			}
		}
	}
	if (smnt) {
		for (i=smnt, j=0; j < nfonts; j++, i=i % nfonts + 1) {
			if (fontbase[i] == NULL)
				continue;
			if ((c = postchar1(temp, i)) != 0 && fchartab[c] == 0) {
				*fp = i;
				return c;
			}
		}
	}
	return 0;
}

static const struct amap {
	const char *alias;
	const char *trname;
} amap[] = {
	{ "lq", "``" },
	{ "rq", "''" },
	{ NULL, NULL }
};

tchar
setch(int delim) {
	register int j;
	char	temp[NC];
	tchar	c, e[2] = {0, 0};
	int	f;
	size_t	n;
	const struct amap *ap;

	n = 0;
	if (delim == 'C')
		e[0] = getach();
	do {
		c = getach();
		if (c == 0 && n < 2)
			return(0);
		if (n >= sizeof temp) {
			temp[n-1] = 0;
			break;
		}
		if ((delim == '[' && c == ']') || (delim == 'C' && c == e[0])) {
			temp[n] = 0;
			break;
		}
		temp[n++] = c;
		if (delim == '(' && n == 2) {
			temp[n] = 0;
			break;
		}
	} while (c);
	for (ap = amap; ap->alias; ap++)
		if (!strcmp(ap->alias, temp)) {
			size_t l;
			const char *s = ap->trname;
			if ((l = strlen(s) + 1) > NC) {
				fprintf(stderr, "%s %i: strlen(%s)+1 > %d\n",
				    __FILE__, __LINE__, s, NC);
				break;
			}
			memcpy(temp, s, l);
			break;
		}
	if (delim == '[' && c != ']') {
		nodelim(']');
		return ' ';
	}
	if (delim == 'C' && c != e[0]) {
		nodelim(e[0]);
		return ' ';
	}
	c = 0;
	if (delim == '[' || delim == 'C') {
		size_t l = strlen(temp);
		if (gemu) {
			if (l == 5 && *temp == 'u'
			    && isxdigit((unsigned)temp[1])
			    && isxdigit((unsigned)temp[2])
			    && isxdigit((unsigned)temp[3])
			    && isxdigit((unsigned)temp[4])) {
				n = strtol(temp + 1, NULL, 16);
				if (n)
					c = setuc0(n);
			} else if ((l == 6 || (l == 7
			    && isdigit((unsigned)temp[6])))
			    && isdigit((unsigned)temp[5])
			    && isdigit((unsigned)temp[4])
			    && !strncmp(temp, "char", 4)) {
				int i = atoi(temp + 4);
				if (i <= 127)
					c = i + nchtab + 128;
			}
		}
		if (!c && (c = postchar(temp, &f))) {
			c |= chbits & ~FMASK;
			setfbits(c, f);
		}
	}
	if (c == 0)
		for (j = 0; j < nchtab; j++)
			if (strcmp(&chname[chtab[j]], temp) == 0) {
				c = (j + 128) | chbits;
				break;
			}
	if (c == 0 && delim == '(')
		if ((c = postchar(temp, &f)) != 0) {
			c |= chbits & ~FMASK;
			setfbits(c, f);
		}
	if (c == 0 && (c = _ps2cc(temp, 0)) != 0) {
		c += nchtab + 128 + 32 + 128 - 32 + nchtab;
		if (chartab[c] == NULL)
			c = 0;
	}
	if (c == 0 && warn & WARN_CHAR)
		errprint("missing glyph \\%c%s%s%s%s", delim, e, temp, e,
				delim == '[' ? "]" : "");
	if (c == 0 && !tryglf)
		c = ' ';
	return c;
}

tchar setabs(void)		/* set absolute char from \C'...' */
{
	int n;

	getch();
	n = 0;
	n = inumb(&n);
	getch();
	if (nonumb || n + nchtab + 128 >= NCHARS)
		return 0;
	return n + nchtab + 128;
}



int
findft(register int i, int required)
{
	register int k;
	int nk;
	const char	*mn;
	char *mp;

	if ((k = i - '0') >= 0 && k <= nfonts && k < smnt && fontbase[k])
		return(k);
	for (k = 0; k > nfonts || fontlab[k] != i; k++)
		if (k > nfonts) {
			mn = macname(i);
			nk = k;
			if ((k = strtol(mn, &mp, 10)) >= 0 && *mp == 0 &&
					mp > mn && k <= nfonts && fontbase[k])
				break;
			if (setfp(nk, i, NULL) == -1)
				return -1;
			else {
				fontlab[nk] = i;
				return nk;
			}
			if (required && warn & WARN_FONT)
				errprint("%s: no such font", mn);
			return(-1);
		}
	return(k);
}

void
caseps(void)
{
	register int i;

	if (skip(0))
		i = apts1;
	else {
		if (xflag == 0) {
			noscale++;
			apts = u2pts(apts);
		} else {
			dfact = INCH;
			dfactd = 72;
			res = VERT;
		}
		i = inumb(&apts);
		if (xflag == 0) {
			noscale--;
			i = pts2u(i);
			apts = pts2u(apts);
		}
		if (nonumb)
			return;
	}
	casps1(i);
}

void
casps1(register int i)
{

/*
 * in olden times, it used to ignore changes to 0 or negative.
 * this is meant to allow the requested size to be anything,
 * in particular so eqn can generate lots of \s-3's and still
 * get back by matching \s+3's.

	if (i <= 0)
		return;
*/
	apts1 = apts;
	apts = i;
	pts1 = pts;
	pts = findps(i);
	mchbits();
}

int
findps(register int i)
{
	register int j, k;

	if (dev.anysize && xflag) {
		if (i <= 0)
			i = pstab[0];
		return i;
	}
	for (j=k=0 ; pstab[j] != 0 ; j++)
		if (abs(pstab[j]-i) < abs(pstab[k]-i))
			k = j;

	return(pstab[k]);
}

void
mchbits(void)
{
	register int i, j, k;

	i = pts;
	if (dev.anysize && xflag)
		j = i - 1;
	else for (j = 0; i > (k = pstab[j]); j++)
		if (!k) {
			k = pstab[--j];
			break;
		}
	chbits = 0;
	setsbits(chbits, ++j);
	setfbits(chbits, font);
	zapwcache(1);
	if (minspsz) {
		k = spacesz;
		spacesz = minspsz;
		minsps = width(' ' | chbits);
		spacesz = k;
		zapwcache(1);
	}
	if (letspsz) {
		k = spacesz;
		spacesz = letspsz;
		letsps = width(' ' | chbits);
		spacesz = k;
		zapwcache(1);
	}
	k = spacesz;
	spacesz = sesspsz;
	ses = width(' ' | chbits);
	spacesz = k;
	zapwcache(1);
	sps = width(' ' | chbits);
	zapwcache(1);
}

void
setps(void)
{
	tchar	c;
	register int i, j = 0;
	int	k;

	i = cbits(c = getch());
	if (ismot(c) && xflag)
		return;
	if (ischar(i) && isdigit(i)) {		/* \sd or \sdd */
		i -= '0';
		if (i == 0)		/* \s0 */
			j = apts1;
		else if (i <= 3 && ischar(j = cbits(ch = getch())) &&
		    isdigit(j)) {	/* \sdd */
			j = 10 * i + j - '0';
			ch = 0;
			j = pts2u(j);
		} else		/* \sd */
			j = pts2u(i);
	} else if (i == '(') {		/* \s(dd */
		j = cbits(getch()) - '0';
		j = 10 * j + cbits(getch()) - '0';
		if (j == 0)		/* \s(00 */
			j = apts1;
		else
			j = pts2u(j);
	} else if (i == '+' || i == '-') {	/* \s+, \s- */
		j = cbits(c = getch());
		if (ischar(j) && isdigit(j)) {		/* \s+d, \s-d */
			j -= '0';
			j = pts2u(j);
		} else if (j == '(') {		/* \s+(dd, \s-(dd */
			j = cbits(getch()) - '0';
			j = 10 * j + cbits(getch()) - '0';
			j = pts2u(j);
		} else if (xflag) {	/* \s+[dd], */
			k = j == '[' ? ']' : j;			/* \s-'dd' */
			setcbits(c, k);
			dfact = INCH;
			dfactd = 72;
			res = HOR;
			j = hatoi();
			res = dfactd = dfact = 1;
			if (nonumb)
				return;
			if (!issame(getch(), c))
				nodelim(k);
		}
		if (i == '-')
			j = -j;
		j += apts;
	} else if (xflag) {	/* \s'+dd', \s[dd] */
		if (i == '[') {
			i = ']';
			setcbits(c, i);
		}
		dfact = INCH;
		dfactd = 72;
		res = HOR;
		j = inumb2(&apts, &k);
		if (nonumb)
			return;
		if (j == 0 && k == 0)
			j = apts1;
		if (!issame(getch(), c))
			nodelim(i);
	}
	casps1(j);
}


tchar setht(void)		/* set character height from \H'...' */
{
	int n;
	tchar c;

	getch();
	dfact = INCH;
	dfactd = 72;
	res = VERT;
	n = inumb(&apts);
	getch();
	if (n == 0 || nonumb)
		n = apts;	/* does this work? */
	c = CHARHT;
	c |= ZBIT;
	setfbits(c, font);
	setsbits(c, n);
	return(c);
}

tchar setslant(void)		/* set slant from \S'...' */
{
	int n;
	tchar c;

	getch();
	n = 0;
	n = inumb(&n);
	getch();
	if (nonumb)
		n = 0;
	c = SLANT;
	c |= ZBIT;
	setfbits(c, font);
	setsbits(c, n+180);
	return(c);
}

void
caseft(void)
{
	skip(0);
	setfont(1);
}

void
setfont(int a)
{
	register int i, j;

	if (a)
		i = getrq(3);
	else
		i = getsn(1);
	if (!i || i == 'P') {
		j = font1;
		goto s0;
	}
	if (/* i == 'S' || */ i == '0')
		return;
	if ((j = findft(i, 0)) == -1)
		if ((j = setfp(0, i, 0)) == -1) { /* try to put it in position 0 */
			if (xflag) {
				font1 = font;
			}
			return;
		}
s0:
	font1 = font;
	font = j;
	mchbits();
}

void
setwd(void)
{
	register int base, wid;
	register tchar i;
	tchar	delim, lasti = 0;
	int	emsz, k;
	int	savhp, savapts, savapts1, savfont, savfont1, savpts, savpts1;
	int	savlgf;
	int	rst = 0, rsb = 0;
	int	n;

	base = numtab[SB].val = numtab[ST].val = wid = numtab[CT].val = 0;
	if (ismot(i = getch()))
		return;
	delim = i;
	argdelim = delim;
	n = noschr;
	noschr = 0;
	savhp = numtab[HP].val;
	numtab[HP].val = 0;
	savapts = apts;
	savapts1 = apts1;
	savfont = font;
	savfont1 = font1;
	savpts = pts;
	savpts1 = pts1;
	savlgf = lgf;
	lgf = 0;
	setwdf++;
	while (i = getch(), !issame(i, delim) && !nlflg) {
		k = width(i);
		k += kernadjust(lasti, i);
		lasti = i;
		wid += k;
		numtab[HP].val += k;
		if (!ismot(i)) {
			emsz = POINT * u2pts(xpts);
		} else if (isvmot(i)) {
			k = absmot(i);
			if (isnmot(i))
				k = -k;
			base -= k;
			emsz = 0;
		} else
			continue;
		if (base < numtab[SB].val)
			numtab[SB].val = base;
		if ((k = base + emsz) > numtab[ST].val)
			numtab[ST].val = k;
		if (lastrst > rst)
			rst = lastrst;
		if (lastrsb < rsb)
			rsb = lastrsb;
	}
	if (!issame(i, delim))
		nodelim(delim);
	argdelim = 0;
	noschr = n;
	setn1(wid, 0, (tchar) 0);
	prwatchn(&numtab[CT]);
	prwatchn(&numtab[SB]);
	prwatchn(&numtab[ST]);
	setnr("rst", rst, 0);
	setnr("rsb", rsb, 0);
	numtab[HP].val = savhp;
	apts = savapts;
	apts1 = savapts1;
	font = savfont;
	font1 = savfont1;
	pts = savpts;
	pts1 = savpts1;
	lgf = savlgf;
	mchbits();
	setwdf = 0;
}


tchar vmot(void)
{
	dfact = lss;
	vflag++;
	return(mot());
}


tchar hmot(void)
{
	dfact = EM;
	return(mot());
}


tchar mot(void)
{
	register int j, n;
	register tchar i;
	tchar c, delim;

	j = HOR;
	delim = getch(); /*eat delim*/
	if ((n = hatoi())) {
		if (vflag)
			j = VERT;
		i = makem(quant(n, j));
	} else
		i = 0;
	c = getch();
	if (!issame(c, delim))
		nodelim(delim);
	vflag = 0;
	dfact = 1;
	return(i);
}


tchar sethl(int k)
{
	register int j;
	tchar i;

	j = EM / 2;
	if (k == 'u')
		j = -j;
	else if (k == 'r')
		j = -2 * j;
	vflag++;
	i = makem(j);
	vflag = 0;
	return(i);
}


tchar makem(register int i)
{
	register tchar j;

	if ((j = i) < 0)
		j = -j;
	j = sabsmot(j) | MOT;
	if (i < 0)
		j |= NMOT;
	if (vflag)
		j |= VMOT;
	return(j);
}


tchar getlg(tchar i)
{
	tchar j, k, pb[NC];
	struct lgtab *lp;
	int c, f;
	size_t n, lgn;

	f = fbits(i);
	if (lgtab[f] == NULL)	/* font lacks ligatures */
		return(i);
	c = cbits(i);
	lp = &lgtab[f][c];
	if (lp->from != c || (lp = lp->link) == NULL)
		return(i);
	k = i;
	n = 1;
	lgn = lg == 2 ? 2 : 1000;
	for (;;) {
		j = getch0();
		if (n < sizeof pb)
			pb[n-1] = j;
		c = cbits(j);
		while (lp != NULL && lp->from != c)
			lp = lp->next;
		if (lp == NULL || lp->to == 0) {
			pbbuf[pbp++] = j;
			return(k);
		}
		if (lp->to == -1) {	/* fdeferlig request */
			pb[n < sizeof pb ? n : sizeof pb - 1] = 0;
			pushback(pb);
			return(i);
		}
		k = (i & SFMASK) | lp->to | AUTOLIG;
		if (lp->link == NULL || ++n > lgn)
			return(k);
		lp = lp->link;
	}
}

int
strlg(int f, int *tp, int n)
{
	struct lgtab	*lp;
	int	i;

	if (n == 1)
		return tp[0];
	if (lgtab[f] == NULL)
		return 0;
	lp = &lgtab[f][tp[0]];
	if (lp->from != tp[0])
		return 0;
	for (i = 1; i < n; i++) {
		if ((lp = lp->link) == NULL)
			return 0;
		while (lp != NULL && lp->from != tp[i])
			lp = lp->next;
		if (lp == NULL || lp->to == 0)
			return 0;
	}
	return lp->to > 0 ? lp->to : 0;
}

void
caselg(void)
{

	lg = 1;
	if (skip(0))
		return;
	lg = hatoi();
}

static void
addlig(int f, tchar *from, int to)
{
	int	i, j;
	struct lgtab	*lp;

	if (from[0] == 0 || from[1] == 0) {
		if (warn & WARN_FONT)
			errprint("short ligature has no effect");
		return;
	}
	if (lgtab[f] == NULL)
		lgtab[f] = calloc(NCHARS, sizeof **lgtab);
	i = cbits(from[0]);
	gchtab[i] |= LGBIT;
	lp = &lgtab[f][i];
	lp->from = i;
	j = 1;
	for (;;) {
		i = cbits(from[j]);
		if (lp->link == NULL) {
			if (from[j+1] != 0) {
				if (warn & WARN_FONT)
					errprint("ligature step missing");
				return;
			}
			lp->link = calloc(1, sizeof *lp->link);
			lp = lp->link;
			lp->from = i;
			lp->to = to;
			break;
		}
		lp = lp->link;
		if (++j >= 4) {
			if (warn & WARN_FONT)
				errprint("ignoring ligature of length >4");
			return;
		}
		while (lp->from != i && lp->next)
			lp = lp->next;
		if (lp->from != i) {
			if (from[j] != 0) {
				if (warn & WARN_FONT)
					errprint("ligature step missing");
				return;
			}
			lp->next = calloc(1, sizeof *lp->next);
			lp = lp->next;
			lp->from = i;
		}
		if (from[j] == 0) {
			lp->to = to;
			break;
		}
	}
	if (to >= 0) {
		if (lgrevtab[f] == NULL)
			lgrevtab[f] = calloc(NCHARS, sizeof **lgrevtab);
		lgrevtab[f][to] = malloc((j+2) * sizeof ***lgrevtab);
		j = 0;
		while ((lgrevtab[f][to][j] = cbits(from[j])))
			j++;
	}
	/*
	 * If the font still contains the charlib substitutes for ff,
	 * Fi, and Fl, hide them. The ".flig" request is intended for
	 * use in combination with expert fonts only.
	 */
	if ((to == LIG_FF || (cbits(from[0]) == 'f' && cbits(from[1]) == 'f' &&
			      cbits(from[2]) == 0)) &&
			fitab[f][LIG_FF-32] != NOCODE)
		if (codetab[f][fitab[f][LIG_FF-32]] < 32)
			fitab[f][LIG_FF-32] = 0;
	if ((to == LIG_FFI || (cbits(from[0]) == 'f' && cbits(from[1]) == 'f' &&
			       cbits(from[2]) == 'i' && cbits(from[3]) == 0)) &&
			fitab[f][LIG_FFI-32] != NOCODE)
		if (codetab[f][fitab[f][LIG_FFI-32]] < 32)
			fitab[f][LIG_FFI-32] = 0;
	if ((to == LIG_FFL || (cbits(from[0]) == 'f' && cbits(from[1]) == 'f' &&
			       cbits(from[2]) == 'l' && cbits(from[3]) == 0)) &&
			fitab[f][LIG_FFL-32] != NOCODE)
		if (codetab[f][fitab[f][LIG_FFL-32]] < 32)
			fitab[f][LIG_FFL-32] = 0;
}

static void
dellig(int f, tchar *from)
{
	struct lgtab	*lp, *lq;
	int	i, j;

	if (from[0] == 0 || from[1] == 0)
		return;
	if (lgtab[f] == NULL)
		return;
	i = cbits(from[0]);
	lp = lq = &lgtab[f][i];
	j = 1;
	for (;;) {
		i = cbits(from[j]);
		if (lp->link == NULL)
			break;
		lq = lp;
		lp = lp->link;
		while (lp->from != i && lp->next) {
			lq = lp;
			lp = lp->next;
		}
		if (lp->from != i)
			break;
		if (from[++j] == 0) {
			if (lq->link == lp)
				lq->link = lp->next;
			else if (lq->next == lp)
				lq->next = lp->next;
			if (lp->link)
				if (warn & WARN_FONT)
					errprint("deleted ligature cuts chain");
			free(lgrevtab[f][lp->to]);
			lgrevtab[f][lp->to] = NULL;
			free(lp);
			break;
		}
	}
}

void
setlig(int f, int j)
{
	tchar	from[4], to;

	free(lgrevtab[f]);
	lgrevtab[f] = NULL;
	free(lgtab[f]);
	lgtab[f] = NULL;
	from[0] = 'f';
	from[2] = from[3] = 0;
	from[1] = 'f';
	to = LIG_FF;
	if (j & LFF)
		addlig(f, from, to);
	from[1] = 'i';
	to = LIG_FI;
	if (j & LFI)
		addlig(f, from, to);
	from[1] = 'l';
	to = LIG_FL;
	if (j & LFL)
		addlig(f, from, to);
	from[1] = 'f';
	from[2] = 'i';
	to = LIG_FFI;
	if (j & LFFI)
		addlig(f, from, to);
	from[2] = 'l';
	to = LIG_FFL;
	if (j & LFFL)
		addlig(f, from, to);
}

static int
getflig(int f, int mode)
{
	int	delete, allnum;
	tchar	from[NC], to;
	int	c;
	size_t	i, j;
	char	number[NC];

	if (skip(0))
		return 0;
	switch (cbits(c = getch())) {
	case '-':
		c = getch();
		delete = 1;
		break;
	case '+':
		c = getch();
		/*FALLTHRU*/
	default:
		delete = 0;
		break;
	case 0:
		return 0;
	}
	allnum = 1;
	for (i = 0; i < sizeof from - 2; i++) {
		from[i] = c;
		j = cbits(c);
		if (c == 0 || ismot(c) || j == ' ' || j == '\n') {
			from[i] = 0;
			ch = j;
			break;
		}
		if (j < '0' || j > '9')
			allnum = 0;
		c = getch();
	}
	if (mode == 0 && allnum == 1) {	/* backwards compatibility */
		if (skip(0) == 0)
			goto new;
		for (j = 0; j <= i+1; j++)
			number[j] = cbits(from[j]);
		j = strtol(number, NULL, 10);
		setlig(f, j);
		return 0;
	}
	if (delete == 0) {
		if (mode >= 0) {
			if (skip(1))
				return 0;
		new:	to = cbits(getch());
		} else
			to = -1;
		addlig(f, from, to);
	} else
		dellig(f, from);
	return 1;
}

void
caseflig(int defer)
{
	int	i, j;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((j = findft(i, 1)) < 0)
		return;
	i = 0;
	while (getflig(j, defer ? -1 : i++) != 0);
}

void
casefdeferlig(void)
{
	caseflig(1);
}

void
casefp(int spec)
{
	register int i, j;
	char *file, *supply;

	lgf++;
	skip(0);
	if ((i = xflag ? hatoi() : cbits(getch()) - '0') < 0 || i > 255)
	bad:	errprint("fp: bad font position %d", i);
	else if (skip(0) || !(j = getrq(3)))
		errprint("fp: no font name");
	else {
		if (skip(0) || !getname()) {
			if (i == 0)
				goto bad;
			setfp(i, j, 0);
		} else {		/* 3rd argument = filename */
			file = strdup(nextf);
			if (!skip(0) && getname()) {
				supply = strdup(nextf);
			} else
				supply = NULL;
			if (loadafm(i?i:-1, j, file, supply, 0, spec) == 0) {
				if (i == 0) {
					if (warn & WARN_FONT)
						errprint("fp: cannot mount %s",
								file);
				} else
					setfp(i, j, file);
			}
			free(file);
			free(supply);
		}
	}
}

void
casefps(void)
{
	const struct {
		enum spec	spec;
		const char	*name;
	} tab[] = {
		{ SPEC_MATH,	"math" },
		{ SPEC_GREEK,	"greek" },
		{ SPEC_PUNCT,	"punct" },
		{ SPEC_LARGE,	"large" },
		{ SPEC_S1,	"S1" },
		{ SPEC_S,	"S" },
		{ SPEC_NONE,	NULL }
	};
	char	name[NC];
	int	c = 0;
	size_t	i;
	enum spec	s = SPEC_NONE;

	if (skip(1))
		return;
	do {
		for (i = 0; i < sizeof name - 2; i++) {
			if ((c = getach()) == 0 || c == ':' || c == ',')
				break;
			name[i] = c;
		}
		name[i] = 0;
		for (i = 0; tab[i].name; i++)
			if (strcmp(tab[i].name, name) == 0) {
				s |= tab[i].spec;
				break;
			}
		if (tab[i].name == NULL)
			errprint("fps: unknown special set %s", name);
	} while (c);
	casefp(s);
}

int
setfp(int pos, int f, char *truename)	/* mount font f at position pos[0...nfonts] */
{
	char longname[4096], *ap;
	const char *shortname;
	char *fpout;
	int i, nw;

	zapwcache(0);
	if (truename)
		shortname = truename;
	else
		shortname = macname(f);
	shortname = mapft(shortname);
	snprintf(longname, sizeof longname, "%s/dev%s/%s",
			fontfile, devname, shortname);
	if ((fpout = readfont(longname, &dev, warn & WARN_FONT)) == NULL)
		return(-1);
	if (pos >= Nfont)
		growfonts(pos+1);
	if (pos > nfonts)
		nfonts = pos;
	fontbase[pos] = (struct Font *)fpout;
	if ((ap = strstr(fontbase[pos]->namefont, ".afm")) != NULL) {
		*ap = 0;
		if (ap == &fontbase[pos]->namefont[1])
			f &= BYTEMASK;
		loadafm(pos, f, fontbase[pos]->namefont, NULL, 1, SPEC_NONE);
		free(fpout);
	} else {
		nw = fontbase[pos]->nwfont & BYTEMASK;
		makefont(pos, &((char *)fontbase[pos])[sizeof(struct Font)],
			&((char *)fontbase[pos])[sizeof(struct Font) + nw],
			&((char *)fontbase[pos])[sizeof(struct Font) + 2*nw],
			&((char *)fontbase[pos])[sizeof(struct Font) + 3*nw],
			nw);
		setlig(pos, fontbase[pos]->ligfont);
	}
	if (pos == smnt) {
		smnt = 0;
		sbold = 0;
	}
	if ((fontlab[pos] = f) == 'S')
		smnt = pos;
	bdtab[pos] = cstab[pos] = ccstab[pos] = 0;
	zoomtab[pos] = 0;
	fallbacktab[pos] = NULL;
	free(lhangtab[pos]);
	lhangtab[pos] = NULL;
	free(rhangtab[pos]);
	rhangtab[pos] = NULL;
	memset(&tracktab[pos], 0, sizeof tracktab[pos]);
	for (i = 0; i < NCHARS; i++)
		ftrtab[pos][i] = i;
	kzap(pos);
		/* if there is a directory, no place to store its name. */
		/* if position isn't zero, no place to store its value. */
		/* only time a FONTPOS is pushed back is if it's a */
		/* standard font on position 0 (i.e., mounted implicitly. */
		/* there's a bug here:  if there are several input lines */
		/* that look like .ft XX in short successtion, the output */
		/* will all be in the last one because the "x font ..." */
		/* comes out too soon.  pushing back FONTPOS doesn't work */
		/* with .ft commands because input is flushed after .xx cmds */
	if (realpage && ap == NULL)
		ptfpcmd(pos, shortname, NULL, 0);
	if (pos == 0)
		ch = (tchar) FONTPOS | (tchar) f << 22;
	return(pos);
}

int
nextfp(void)
{
	int	i;

	for (i = 1; i <= nfonts; i++)
		if (fontbase[i] == NULL)
			return i;
	if (i <= 255)
		return i;
	return 0;
}

void
casecs(void)
{
	register int i, j;

	noscale++;
	if (skip(1))
		goto rtn;
	if (!(i = getrq(2)))
		goto rtn;
	if ((i = findft(i, 1)) < 0)
		goto rtn;
	skip(0);
	cstab[i] = hatoi();
	skip(0);
	j = hatoi();
	if (nonumb)
		ccstab[i] = 0;
	else
		ccstab[i] = findps(j);
rtn:
	zapwcache(0);
	noscale = 0;
}

void
casebd(void)
{
	register int i, j = 0, k;

	zapwcache(0);
	k = 0;
bd0:
	if (skip(1) || !(i = getrq(2)) || (j = findft(i, 1)) == -1) {
		if (k)
			goto bd1;
		else
			return;
	}
	if (j == smnt) {
		k = smnt;
		goto bd0;
	}
	if (k) {
		sbold = j;
		j = k;
	}
bd1:
	skip(0);
	noscale++;
	bdtab[j] = hatoi();
	noscale = 0;
}

void
casevs(void)
{
	register int i;

	skip(0);
	vflag++;
	dfact = INCH; /* default scaling is points! */
	dfactd = 72;
	res = VERT;
	i = inumb(&lss);
	if (nonumb)
		i = lss1;
	if (xflag && i < 0) {
		if (warn & WARN_RANGE)
			errprint("negative vertical spacing ignored");
		i = lss1;
	}
	if (i < VERT)
		i = VERT;
	lss1 = lss;
	lss = i;
}

void
casess(int flg)
{
	register int i, j;

	noscale++;
	if (skip(flg == 0))
		minsps = minspsz = 0;
	else if ((i = hatoi()) != 0 && !nonumb) {
		if (xflag && flg == 0 && !skip(0)) {
			j = hatoi();
			if (!nonumb) {
				sesspsz = j & 0177;
				spacesz = sesspsz;
				zapwcache(1);
				ses = width(' ' | chbits);
			}
		}
		if (flg) {
			j = spacesz;
			minspsz = i & 0177;
			spacesz = minspsz;
			zapwcache(1);
			minsps = width(' ' | chbits);
			spacesz = j;
			zapwcache(0);
			sps = width(' ' | chbits);
		} else {
			spacesz = i & 0177;
			zapwcache(0);
			sps = width(' ' | chbits);
			if (minspsz > spacesz)
				minsps = minspsz = 0;
		}
	}
	noscale = 0;
}

void
caseminss(void)
{
	casess(1);
}

void
caseletadj(void)
{
	int	s, n, x, l, h;

	dfact = LAFACT / 100;
	if (skip(0) || (n = hatoi()) == 0) {
		letspsz = 0;
		letsps = 0;
		lspmin = 0;
		lspmax = 0;
		lshmin = 0;
		lshmax = 0;
		goto ret;
	}
	if (skip(1))
		goto ret;
	dfact = LAFACT / 100;
	l = hatoi();
	if (skip(1))
		goto ret;
	noscale++;
	s = hatoi();
	noscale--;
	if (skip(1))
		goto ret;
	dfact = LAFACT / 100;
	x = hatoi();
	if (skip(1))
		goto ret;
	dfact = LAFACT / 100;
	h = hatoi();
	letspsz = s;
	lspmin = LAFACT - n;
	lspmax = x - LAFACT;
	lshmin = LAFACT - l;
	lshmax = h - LAFACT;
	s = spacesz;
	spacesz = letspsz;
	zapwcache(1);
	letsps = width(' ' | chbits);
	spacesz = s;
	zapwcache(1);
	sps = width(' ' | chbits);
	zapwcache(1);
ret:
	dfact = 1;
}

void
casefspacewidth(void)
{
	int	f, n, i;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((f = findft(i, 1)) < 0)
		return;
	if (skip(0)) {
		fontbase[f]->cspacewidth = -1;
		fontab[f][0] = fontbase[f]->spacewidth;
	} else {
		noscale++;
		n = hatoi();
		noscale--;
		unitsPerEm = 1000;
		if (n >= 0)
			fontbase[f]->cspacewidth = fontab[f][0] = _unitconv(n);
		else if (warn & WARN_RANGE)
			errprint("ignoring negative space width %d", n);
	}
	zapwcache(1);
}

void
casespacewidth(void)
{
	noscale++;
	spacewidth = skip(0) || hatoi();
	noscale--;
}

tchar xlss(void)
{
	/* stores \x'...' into
	 * two successive tchars.
	 * the first contains HX, the second the value,
	 * encoded as a vertical motion.
	 * decoding is done in n2.c by pchar().
	 */
	int	i;

	getch();
	dfact = lss;
	i = quant(hatoi(), VERT);
	dfact = 1;
	getch();
	if (i >= 0)
		pbbuf[pbp++] = MOT | VMOT | sabsmot(i);
	else
		pbbuf[pbp++] = MOT | VMOT | NMOT | sabsmot(-i);
	return(HX);
}

struct afmtab **afmtab;
int nafm;

static char *
onefont(char *prefix, char *file, char *type)
{
	char	*path, *fp, *tp;
	size_t	l;

	l = strlen(prefix) + strlen(file) + 2;
	path = malloc(l);
	n_strcpy(path, prefix, l);
	n_strcat(path, "/", l);
	n_strcat(path, file, l);
	if (type) {
		for (fp = file; *fp; fp++);
		for (tp = type; *tp; tp++);
		while (tp >= type && fp >= file && *fp-- == *tp--);
		if (tp >= type) {
			l = strlen(path) + strlen(type) + 2;
			tp = malloc(l);
			n_strcpy(tp, path, l);
			n_strcat(tp, ".", l);
			n_strcat(tp, type, l);
			free(path);
			path = tp;
		}
	}
	return path;
}

static char *
getfontpath(char *file, char *type)
{
	char	*path, *troffonts, *tp, *tq, c;
	size_t	l;

	if ((troffonts = getenv("TROFFONTS")) != NULL) {
		tp = strdup(troffonts);
		troffonts = tp;
		do {
			for (tq = tp; *tq && *tq != ':'; tq++);
			c = *tq;
			*tq = 0;
			path = onefont(tp, file, type);
			if (access(path, 0) == 0) {
				free(troffonts);
				return path;
			}
			free(path);
			tp = &tq[1];
		} while (c);
		free(troffonts);
	}
	l = strlen(fontfile) + strlen(devname) + 10;
	tp = malloc(l);
	snprintf(tp, l, "%s/dev%s", fontfile, devname);
	path = onefont(tp, file, type);
	free(tp);
	return path;
}

static void
checkenminus(int f)
{
	/*
	 * A fix for a very special case: If the font supplies punctuation
	 * characters but is not S1, only one of \- and \(en is present
	 * since the PostScript character "endash" is mapped to both of
	 * them.
	 */
	enum spec	spec;
	int	i;

	if (afmtab == NULL || (i = fontbase[f]->afmpos - 1) < 0)
		return;
	if (c_endash == 0 || c_minus == 0)
		specnames();
	spec = afmtab[i]->spec;
	if ((spec&(SPEC_PUNCT|SPEC_S1)) == SPEC_PUNCT) {
		if (fitab[f][c_endash-32] == 0 && ftrtab[f][c_minus-32])
			ftrtab[f][c_endash] = c_minus;
		else if (fitab[f][c_endash-32] && ftrtab[f][c_minus-32] != 0)
			ftrtab[f][c_minus] = c_endash;
	}
}

int
loadafm(int nf, int rq, char *file, char *supply, int required, enum spec spec)
{
	struct stat	st;
	int	fd;
	char	*path, *contents;
	struct afmtab	*a;
	int	i, have = 0;
	struct namecache	*np;

	zapwcache(0);
	if (nf < 0)
		nf = nextfp();
	path = getfontpath(file, "afm");
	if (access(path, 0) < 0) {
		path = getfontpath(file, "otf");
		if (access(path, 0) < 0)
			path = getfontpath(file, "ttf");
	}
	if (dev.allpunct)
		spec |= SPEC_PUNCT;
	a = calloc(1, sizeof *a);
	for (i = 0; i < nafm; i++)
		if (strcmp(afmtab[i]->path, path) == 0 &&
				afmtab[i]->spec == spec) {
			*a = *afmtab[i];
			have = 1;
			break;
		}
	a->path = path;
	a->file = strdup(file);
	a->spec = spec;
	a->rq = rq;
	a->Font.namefont[0] = rq&0377;
	a->Font.namefont[1] = (rq>>8)&0377;
	snprintf(a->Font.intname, sizeof(a->Font.intname), "%d", nf);
	if (have)
		goto done;
	if ((fd = open(path, O_RDONLY)) < 0) {
		if (required)
			errprint("Can't open %s", path);
		free(a->file);
		free(a);
		free(path);
		return 0;
	}
	if (fstat(fd, &st) < 0) {
		errprint("Can't stat %s", path);
		free(a->file);
		free(a);
		free(path);
		return -1;
	}
	contents = malloc(st.st_size + 1);
	if (read(fd, contents, st.st_size) != st.st_size) {
		errprint("Can't read %s", path);
		free(a->file);
		free(a);
		free(path);
		free(contents);
		return -1;
	}
	contents[st.st_size] = 0;
	close(fd);
	if (afmget(a, contents, st.st_size) < 0) {
		free(path);
		free(contents);
		return -1;
	}
	free(contents);
	morechars(a->nchars+32+1+128-32+nchtab+32+nchtab+128+psmaxcode+1);
done:	afmtab = realloc(afmtab, (nafm+1) * sizeof *afmtab);
	afmtab[nafm] = a;
	if (nf >= Nfont)
		growfonts(nf+1);
	a->Font.afmpos = nafm+1;
	if ((np = afmnamelook(a, "space")) != NULL)
		a->Font.spacewidth = a->fontab[np->afpos];
	else
		a->Font.spacewidth = a->fontab[0];
	a->Font.cspacewidth = -1;
	fontbase[nf] = &afmtab[nafm]->Font;
	fontlab[nf] = rq;
	free(fontab[nf]);
	free(kerntab[nf]);
	free(codetab[nf]);
	free(fitab[nf]);
	fontab[nf] = malloc(a->nchars * sizeof *fontab[nf]);
	kerntab[nf] = malloc(a->nchars * sizeof *kerntab[nf]);
	codetab[nf] = malloc(a->nchars * sizeof *codetab[nf]);
	fitab[nf] = calloc(NCHARS, sizeof *fitab[nf]);
	memcpy(fontab[nf], a->fontab, a->nchars * sizeof *fontab[nf]);
	memcpy(kerntab[nf], a->kerntab, a->nchars * sizeof *kerntab[nf]);
	memcpy(codetab[nf], a->codetab, a->nchars * sizeof *codetab[nf]);
	memcpy(fitab[nf], a->fitab, a->fichars * sizeof *fitab[nf]);
	bdtab[nf] = cstab[nf] = ccstab[nf] = 0;
	zoomtab[nf] = 0;
	fallbacktab[nf] = NULL;
	free(lhangtab[nf]);
	lhangtab[nf] = NULL;
	free(rhangtab[nf]);
	rhangtab[nf] = NULL;
	memset(&tracktab[nf], 0, sizeof tracktab[nf]);
	setlig(nf, a->Font.ligfont);
	for (i = 0; i < NCHARS; i++)
		ftrtab[nf][i] = i;
	kzap(nf);
	nafm++;
	if (nf > nfonts)
		nfonts = nf;
	if (supply) {
		char	*data;
		if (strcmp(supply, "pfb") == 0 || strcmp(supply, "pfa") == 0 ||
				strcmp(supply, "t42") == 0 ||
				strcmp(supply, "otf") == 0 ||
				strcmp(supply, "ttf") == 0)
			data = getfontpath(file, supply);
		else
			data = getfontpath(supply, NULL);
		a->supply = afmencodepath(data);
		free(data);
		if (realpage)
			ptsupplyfont(a->fontname, a->supply);
	}
	checkenminus(nf);
	if (realpage)
		ptfpcmd(nf, macname(fontlab[nf]), a->path, (int)a->spec);
	return 1;
}

static int
tracknum(void)
{
	skip(1);
	dfact = INCH;
	dfactd = 72;
	res = VERT;
	return inumb(NULL);
}

void
casetrack(void)
{
	int	i, j, s1, n1, s2, n2;

	if (skip(1))
		return;
	i = getrq(2);
	if ((j = findft(i, 1)) < 0)
		return;
	s1 = tracknum();
	if (!nonumb) {
		n1 = tracknum();
		if (!nonumb) {
			s2 = tracknum();
			if (!nonumb) {
				n2 = tracknum();
				if (!nonumb) {
					tracktab[j].s1 = s1;
					tracktab[j].n1 = n1;
					tracktab[j].s2 = s2;
					tracktab[j].n2 = n2;
					zapwcache(0);
				}
			}
		}
	}
}

void
casefallback(void)
{
	int	*fb = NULL;
	int	i, j, n = 0;

	if (skip(1))
		return;
	i = getrq(2);
	if ((j = findft(i, 1)) < 0)
		return;
	do {
		skip(0);
		i = getrq(2);
		fb = realloc(fb, (n+2) * sizeof *fb);
		fb[n++] = i;
	} while (i);
	fallbacktab[j] = fb;
}

void
casehidechar(void)
{
	int	savfont = font, savfont1 = font1;
	int	i, j;
	tchar	k;

	if (skip(1))
		return;
	i = getrq(2);
	if ((j = findft(i, 1)) < 0)
		return;
	font = font1 = j;
	mchbits();
	while ((i = cbits(k = getch())) != '\n') {
		if (fbits(k) != j || ismot(k) || i == ' ')
			continue;
		if (i >= nchtab + 128-32 && afmtab &&
				fontbase[j]->afmpos - 1 >= 0)
			i -= nchtab + 128;
		fitab[j][i - 32] = 0;
	}
	font = savfont;
	font1 = savfont1;
	mchbits();
	zapwcache(0);
}

void
casefzoom(void)
{
	int	i, j;
	float	f;

	if (skip(1))
		return;
	i = getrq(2);
	if ((j = findft(i, 1)) < 0)
		return;
	skip(1);
	f = atof();
	if (!nonumb && f >= 0) {
		zoomtab[j] = f;
		zapwcache(0);
		if (realpage && j == xfont && !ascii)
			ptps();
	}
}

double
getfzoom(void)
{
	return zoomtab[font];
}

void
casekern(void)
{
	kern = skip(0) || hatoi() ? 1 : 0;
}

void
casefkern(void)
{
	int	f, i, j;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((f = findft(i, 1)) < 0)
		return;
	if (skip(0))
		fontbase[f]->kernfont = 0;
	else {
		j = hatoi();
		if (!nonumb)
			fontbase[f]->kernfont = j ? j : -1;
	}
}

static void
setpapersize(int setmedia)
{
	const struct {
		const char	*name;
		int	width;
		int	heigth;
	} papersizes[] = {
		{ "executive",	 518,	 756 },
		{ "letter",	 612,	 792 },
		{ "legal",	 612,	 992 },
		{ "ledger",	1224,	 792 },
		{ "tabloid",	 792,	1224 },
		{ "a0",		2384,	3370 },
		{ "a1",		1684,	2384 },
		{ "a2",		1191,	1684 },
		{ "a3",		 842,	1191 },
		{ "a4",		 595,	 842 },
		{ "a5",		 420,	 595 },
		{ "a6",		 298,	 420 },
		{ "a7",		 210,	 298 },
		{ "a8",		 147,	 210 },
		{ "a9",		 105,	 147 },
		{ "a10",	  74,	 105 },
		{ "b0",		2835,	4008 },
		{ "b1",		2004,	2835 },
		{ "b2",		1417,	2004 },
		{ "b3",		1000,	1417 },
		{ "b4",		 709,	1000 },
		{ "b5",		 499,	 709 },
		{ "b6",		 354,	 499 },
		{ "b7",		 249,	 354 },
		{ "b8",		 176,	 249 },
		{ "b9",		 125,	 176 },
		{ "b10",	  87,	 125 },
		{ "c0",		2599,	3677 },
		{ "c1",		1837,	2599 },
		{ "c2",		1298,	1837 },
		{ "c3",		 918,	1298 },
		{ "c4",		 649,	 918 },
		{ "c5",		 459,	 649 },
		{ "c6",		 323,	 459 },
		{ "c7",		 230,	 323 },
		{ "c8",		 162,	 230 },
		{ NULL,		   0,	   0 }
	};
	int	c;
	int	x = 0, y = 0;
	size_t	n;
	char	buf[NC];

	lgf++;
	if (skip(1))
		return;
	c = cbits(ch);
	if (isdigit(c) || c == '(') {
		x = hatoi();
		if (!nonumb) {
			skip(1);
			y = hatoi();
		}
		if (nonumb || x == 0 || y == 0)
			return;
	} else {
		n = 0;
		do {
			c = getach();
			if (n+1 < sizeof buf)
				buf[n++] = c;
		} while (c);
		buf[n] = 0;
		for (n = 0; papersizes[n].name != NULL; n++)
			if (strcmp(buf, papersizes[n].name) == 0) {
				x = papersizes[n].width * INCH / 72;
				y = papersizes[n].heigth * INCH / 72;
				break;
			}
		if (x == 0 || y == 0) {
			errprint("Unknown paper size %s", buf);
			return;
		}
	}
	pl = defaultpl = y;
	if (numtab[NL].val > pl) {
		numtab[NL].val = pl;
		prwatchn(&numtab[NL]);
	}
	po = x > 6 * PO ? PO : x / 8;
	ll = ll1 = lt = lt1 = x - 2 * po;
	setnel();
	mediasize.val[2] = x;
	mediasize.val[3] = y;
	mediasize.flag |= 1;
	if (setmedia)
		mediasize.flag |= 2;
	if (realpage)
		ptpapersize();
}

void
casepapersize(void)
{
	setpapersize(0);
}

void
casemediasize(void)
{
	setpapersize(1);
}

static void
cutat(struct box *bp)
{
	int	c[4], i;

	for (i = 0; i < 4; i++) {
		if (skip(1))
			return;
		dfact = INCH;
		dfactd = 72;
		c[i] = inumb(NULL);
		if (nonumb)
			return;
	}
	for (i = 0; i < 4; i++)
		bp->val[i] = c[i];
	bp->flag |= 1;
	if (realpage)
		ptcut();
}

void
casetrimat(void)
{
	cutat(&trimat);
}

void
casebleedat(void)
{
	cutat(&bleedat);
}

void
casecropat(void)
{
	cutat(&cropat);
}

void
caselhang(void)
{
	kernsingle(lhangtab);
}

void
caserhang(void)
{
	kernsingle(rhangtab);
}

void
casekernpair(void)
{
	int	savfont = font, savfont1 = font1;
	int	f, g, i, j, n;
	tchar	c, e, *cp = NULL, *dp = NULL;
	int	a = 0, b = 0;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((f = findft(i, 1)) < 0)
		return;
	font = font1 = f;
	mchbits();
	if (skip(1))
		goto done;
	while ((j = cbits(c = getch())) > ' ' || j == UNPAD) {
		if (fbits(c) != f) {
			if (warn & WARN_CHAR)
				errprint("glyph %C not in font %s",
					c, macname(i));
			continue;
		}
		cp = realloc(cp, ++a * sizeof *cp);
		cp[a-1] = c;
	}
	if (a == 0 || skip(1))
		goto done;
	i = getrq(2);
	if ((g = findft(i, 1)) < 0)
		goto done;
	font = font1 = g;
	mchbits();
	if (skip(1))
		goto done;
	while ((j = cbits(c = getch())) > ' ' || j == UNPAD) {
		if (fbits(c) != g) {
			if (warn & WARN_CHAR)
				errprint("glyph %C not in font %s",
					c, macname(i));
			continue;
		}
		dp = realloc(dp, ++b * sizeof *dp);
		dp[b-1] = c;
	}
	if (b == 0 || skip(1))
		goto done;
	noscale++;
	n = hatoi();
	noscale--;
	unitsPerEm = 1000;
	n = _unitconv(n);
	for (i = 0; i < a; i++)
		for (j = 0; j < b; j++) {
			if ((c = cbits(cp[i])) == 0)
				continue;
			if (c == UNPAD)
				c = ' ';
			setfbits(c, f);
			if ((e = cbits(dp[j])) == 0)
				continue;
			if (e == UNPAD)
				e = ' ';
			setfbits(e, g);
			kadd(c, e, n);
		}
done:
	free(cp);
	free(dp);
	font = savfont;
	font1 = savfont1;
	mchbits();
}

static void
kernsingle(int **tp)
{
	int     savfont = font, savfont1 = font1;
	int	f, i, j, n;
	int	twice = 0;
	tchar	c, *cp = NULL;
	int	a;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((f = findft(i, 1)) < 0)
		return;
	font = font1 = f;
	mchbits();
	while (!skip(twice++ == 0)) {
		a = 0;
		while ((j = cbits(c = getch())) > ' ') {
			if (fbits(c) != f) {
				if (warn & WARN_CHAR)
					errprint("glyph %C not in font %s",
						c, macname(i));
				continue;
			}
			cp = realloc(cp, ++a * sizeof *cp);
			cp[a-1] = c;
		}
		if (skip(1))
			break;
		noscale++;
		n = hatoi();
		noscale--;
		if (tp[f] == NULL)
			tp[f] = calloc(NCHARS, sizeof *tp);
		unitsPerEm = 1000;
		n = _unitconv(n);
		for (j = 0; j < a; j++)
			tp[f][cbits(cp[j])] = n;
	}
	free(cp);
	font = savfont;
	font1 = savfont1;
	mchbits();
}

void
casekernafter(void)
{
	kernsingle(kernafter);
}

void
casekernbefore(void)
{
	kernsingle(kernbefore);
}

void
caseftr(void)
{
	int	savfont = font, savfont1 = font1;
	int	f, i, j;
	tchar	k;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((f = findft(i, 1)) < 0)
		return;
	font = font1 = f;
	mchbits();
	if (skip(1))
		goto done;
	while ((i = cbits(k=getch())) != '\n') {
		if (ismot(k))
			goto done;
		if (ismot(k = getch()))
			goto done;
		if ((j = cbits(k)) == '\n')
			j = ' ';
		ftrtab[f][i] = j;
	}
done:
	checkenminus(f);
	font = savfont;
	font1 = savfont1;
	mchbits();
}

static int
getfeature(struct afmtab *a, int f)
{
	char	name[NC];
	int	ch1, ch2, c, j, minus;
	size_t	i;
	struct feature	*fp;

	if (skip(0))
		return 0;
	switch (c = getach()) {
	case '-':
		c = getach();
		minus = 1;
		break;
	case '+':
		c = getach();
		/*FALLTHRU*/
	default:
		minus = 0;
		break;
	case 0:
		return 0;
	}
	for (i = 0; i < sizeof name - 2; i++) {
		name[i] = c;
		if ((c = getach()) == 0)
			break;
	}
	name[i+1] = 0;
	for (i = 0; (fp = a->features[i]); i++)
		if (strcmp(fp->name, name) == 0) {
			for (j = 0; j < fp->npairs; j++) {
				ch1 = fp->pairs[j].ch1;
				ch2 = fp->pairs[j].ch2;
				if (minus) {
					if (ftrtab[f][ch1] == ch2)
						ftrtab[f][ch1] = ch1;
				} else {
					ftrtab[f][ch1] = ch2;
				}
			}
			break;
		}
	if (fp == NULL)
		errprint("no feature named %s in font %s", name, a->fontname);
	return 1;
}

void
casefeature(void)
{
	struct afmtab	*a;
	int	f, i, j;

	lgf++;
	if (skip(1))
		return;
	i = getrq(2);
	if ((f = findft(i, 1)) < 0)
		return;
	if ((j = (fontbase[f]->afmpos) - 1) < 0 ||
			((a = afmtab[j])->type != TYPE_OTF &&
			a->type != TYPE_TTF)) {
		errprint("font %s is not an OpenType font", macname(i));
		return;
	}
	if (a->features == NULL) {
		errprint("font %s has no OpenType features", a->fontname);
		return;
	}
	while (getfeature(a, f) != 0);
}

#include "unimap.h"

static int
ufmap(int c, int f, int *fp)
{
	struct unimap	*up, ***um;
	struct afmtab	*a;
	int	i;

	if ((c&~0xffff) == 0 &&
			(i = (fontbase[f]->afmpos) - 1) >= 0 &&
			(um = (a = afmtab[i])->unimap) != NULL &&
			um[c>>8] != NULL &&
			(up = um[c>>8][c&0377]) != NULL) {
		*fp = f;
		return up->u.code;
	}
	return 0;
}

int
un2tr(int c, int *fp)
{
	extern char	ifilt[];
	struct unimap	*um, *up;
	int	i, j;

	switch (c) {
	case 0x00A0:
		*fp = font;
		return UNPAD;
	case 0x00AD:
		*fp = font;
		return ohc;
	case 0x2002:
		return makem((int)(EM)/2);
	case 0x2003:
		return makem((int)EM);
	case 0x2004:
		return makem((int)EM/3);
	case 0x2005:
		return makem((int)EM/4);
	case 0x2006:
		return makem((int)EM/6);
	case 0x2007:
		return makem(width('0' | chbits));
	case 0x2008:
		return makem(width('.' | chbits));
	case 0x2009:
		return makem((int)EM/6);
	case 0x200A:
		return makem((int)EM/12);
	case 0x2010:
		*fp = font;
		return '-';
	case 0x2027:
		*fp = font;
		return OHC | BLBIT;
	case 0x2060:
		*fp = font;
		return FILLER;
	default:
		if ((i = ufmap(c, font, fp)) != 0)
			return i;
		if ((c&~0xffff) == 0 && unimap[c>>8] != NULL &&
				(um = unimap[c>>8][c&0377]) != NULL) {
			up = um;
			do
				if ((j = postchar1(up->u.psc, font)) != 0) {
					*fp = font;
					return j;
				}
			while ((up = up->next) != NULL);
			up = um;
			do
				if ((j = postchar(up->u.psc, fp)) != 0)
					return j;
			while ((up = up->next) != NULL);
			up = um;
			do
				if ((j = _ps2cc(up->u.psc, 0)) != 0) {
					j += nchtab + 128 + 32 +
						128 - 32 + nchtab;
					if (chartab[j] != NULL)
						return j;
				}
			while ((up = up->next) != NULL);
		}
		if (fallbacktab[font])
			for (j = 0; fallbacktab[font][j] != 0; j++) {
				if ((i = findft(fallbacktab[font][j], 0)) < 0)
					continue;
				if ((i = ufmap(c, i, fp)) != 0)
					return i;
			}
		if (smnt)
			for (i = smnt, j=0; j < nfonts; j++, i = i % nfonts + 1) {
				if (fontbase[i] == NULL)
					continue;
				if ((i = ufmap(c, i, fp)) != 0)
					return i;
			}
		*fp = font;
		if ((c < 040 && c == ifilt[c]) || (c >= 040 && c < 0177))
			return c;
		else if ((c & ~0177) == 0) {
			illseq(c, NULL, 0);
			return 0;
		} else if (defcf && (c & ~0xffff) == 0) {
			char	buf[20];
			snprintf(buf, sizeof(buf), "[uni%04X]", c);
			cpushback(buf);
			unadd(c, NULL);
			return WORDSP;
		} else if (html) {
			return c;
		} else {
			if (warn & WARN_CHAR)
				errprint("no glyph available for %U", c);
			return tryglf ? 0 : ' ';
		}
	}
}

int
tr2un(tchar i, int f)
{
	struct afmtab	*a;
	int	c, n;

	if (i < 32)
		return -1;
	else if (i < 128)
		return i;
	if ((n = (fontbase[f]->afmpos) - 1) >= 0) {
		a = afmtab[n];
		if (a->unitab && i < a->nunitab && a->unitab[i])
			return a->unitab[i];
		if (i - 32 >= nchtab + 128)
			i -= nchtab + 128;
		if ((n = a->fitab[i - 32]) < a->nchars &&
				a->nametab[n] != NULL)
			for (c = 0; rawunimap[c].psc; c++)
				if (strcmp(rawunimap[c].psc, a->nametab[n])==0)
					return rawunimap[c].code;
	}
	return -1;
}

tchar
setuc0(int n)
{
	int	f;
	tchar	c;

	if ((c = un2tr(n, &f)) != 0 && !ismot(c)) {
		c |= chbits & ~FMASK;
		setfbits(c, f);
	}
	return c;
}

static char *
getref(void)
{
	int	a = 0, i, c, delim;
	char	*np = NULL;

	if ((delim = getxch()) != 0) {
		for (i = 0; ; i++) {
			if (i + 1 >= a)
				np = realloc(np, a += 32);
			if ((c = getxch()) == 0) {
				if (cbits(ch) == ' ') {
					ch = 0;
					c = ' ';
				} else {
					nodelim(delim);
					break;
				}
			}
			if (c == delim)
				break;
			np[i] = c;
		}
		np[i] = 0;
	}
	return np;
}

tchar
setanchor(void)
{
	static int	cnt;
	struct ref	*rp;
	char	*np;

	if ((np = getref()) != NULL) {
		rp = calloc(1, sizeof *rp);
		rp->cnt = ++cnt;
		rp->name = np;
		rp->next = anchors;
		anchors = rp;
		return mkxfunc(ANCHOR, cnt);
	} else
		return mkxfunc(ANCHOR, 0);
}

static tchar
_setlink(struct ref **rstart, int oncode, int offcode, int *cnt)
{
	struct ref	*rp;
	char	*np;
	int	_sv;

	_sv = linkin;
	if ((linkin = !linkin)) {
		if ((np = getref()) != NULL) {
			rp = calloc(1, sizeof *rp);
			rp->cnt = ++*cnt;
			rp->name = np;
			rp->next = *rstart;
			*rstart = rp;
			linkin = *cnt;
			return mkxfunc(oncode, *cnt);
		} else {
			linkin = -1;
			return mkxfunc(oncode, 0);
		}
	} else
		return mkxfunc(offcode, _sv > 0 ? _sv : 0);
}

tchar
setlink(void)
{
	static int	cnt;

	return _setlink(&links, LINKON, LINKOFF, &cnt);
}

tchar
setulink(void)
{
	static int	cnt;

	return _setlink(&ulinks, ULINKON, ULINKOFF, &cnt);
}

int
pts2u(int p)
{
	return p * INCH / 72;
}

double
u2pts(int u)
{
	return u * 72.0 / INCH;
}

#define	psnprime	1021

static struct psnnode {
	struct psnnode	*next;
	const char	*name;
	int	code;
} **psntable;

static int
_ps2cc(const char *name, int create)
{
	struct psnnode	*pp;
	unsigned	h;

	if (psntable == NULL)
		psntable = calloc(psnprime, sizeof *psntable);
	h = pjw(name) % psnprime;
	for (pp = psntable[h]; pp; pp = pp->next)
		if (strcmp(name, pp->name) == 0)
			return pp->code;
	if (create == 0)
		return 0;
	pp = calloc(1, sizeof *pp);
	pp->name = strdup(name);
	pp->next = psntable[h];
	psntable[h] = pp;
	return pp->code = ++psmaxcode;
}

int
ps2cc(const char *name)
{
	return _ps2cc(name, 1);
}


void
casewswarn(void)
{
	int	i ;
	float	n ;

	if (skip(0))
		wswarn = 1 ;
	else
		{
		i = hatoi() ;
		if (!nonumb)
			{
			if (i <= 0)
				wswarn = 0 ;
			else if (i == 1)
				wswarn = 1 ;
			else
				wswarn = 2 ;
			}
		if (!skip(0))
			{
			n = atof() ;
			if (!nonumb && n > 0.0 && n <= 100.0)
				wswarnlwr = n / 100.0 ;
			}
		if (!skip(0))
			{
			n = atof() ;
			if (!nonumb && n >= wswarnlwr && n >= 100.0)
				wswarnupr = n / 100.0 ;
			}
		}
}


void
casewsmark(void)
{
	int	n ;

	if (skip(0))
		wsmark = 1;
	else
		{
		n = hatoi();
		if (!nonumb && (n == 0 || n == 1))
			wsmark = n ;
		}
}


void
casewrdspc(void)
{
	float	lowin, highin;

	if (skip(0))
		{
		lowin  = -1.0 ;
		highin = -1.0 ;
		}
	else
		{
		lowin = atof() ;
		if (!nonumb)
			lowin /= 100.0 ;
		else
			lowin = -1.0 ;

		if (skip(1))
			highin = -1.0 ;
		else
			{
			highin = atof() ;
			if (!nonumb)
				highin /= 100.0 ;
			else
				highin = -1.0 ;
			}
		}
	if (lowin >= 0.00 && lowin <= 0.99 && highin >= 1.01 && highin <= 5.00)
		{
		wslwr = lowin ;
		wsupr = highin ;
		}
}


void
casewsmin(void)
{
	double	n ;

	noscale = 1 ;
	if (!skip(0))
		{
		n = atof() ;
		if (!nonumb && n >= 0.0)
			wsmin = n / 100.0 ;
		}
	else
		wsmin = 0 ;
	noscale = 0 ;
}


void
casewscalc(void)
{
	int	n ;

	if (!skip(0))
		{
		dfact = 1 ;
		noscale = 1 ;
		n = hatoi() ;
		if (!nonumb)
			{
			if (n < 0)
				wscalc = 0 ;
			else if (n > 99)
				wscalc = 99 ;
			else
				wscalc = n ;
			}
/*
 *		Heirloom defaults.  wslwr and wsupr are not used with .wscalc 0;
 *		they are set in case the user switches to a different mode later.
 *		The Heirloom equivalent for wslwr is zero, but zero is not a
 *		a valid choice with any of the new modes.
 */
		if (wscalc == 0)
			{
			wslwr = 0.084 ;
			wsupr = 1.6 ;
			wsmin = 0.0 ;
			hypp  = 0.0 ;
			hypp2 = 0.0 ;
			hypp3 = 0.0 ;
			hypp4 = 0.0 ;
			linepenalty = 0.0 ;
			adjpenalty = 0.0 ;
			adjthreshold = 0.5 ;
			adjthreshupr = 0.0 ;
			overrunpenalty = 0.0 ;
			overrunthreshold = 0.25 ;
			overrunmin = 0 ;
			lastlinestretch = 0 ;
			elppen = 0.0 ;
			exhyp = 0.0 ;
			letcalc = 0 ;
			}
/*
 *		TeX82 (10), Knuth-Plass (11), and adapted TeX82 (12) defaults.
 *		All values are from, or adapted from, The TeX Book unless
 *		otherwise noted.  Values have been converted from the TeX scale
 *		to the internal scale.
 *
 *		The hypp hyphenation penalties are converted with PENALSCALE
 *		because that's the way they are read in by casehypp() and kept in
 *		the existing code.  Currently PENALSCALE divides the input by 50.
 *		For wscalc 12, hypp appears to be half of hypp for wscalc 10,
 *		but it is actually squared.  TeX squares hypp (but not hypp2 or
 *		hypp3), wscalc 12 does not.
 */
		if (wscalc == 10 || wscalc == 11 || wscalc == 12)
			{
			wslwr = 0.667 ;
			wsupr = 1.5 ;
			wsmin = 0.0 ;
			adjpenalty = 1.0 ;
			adjthreshold = 0.5 ;
			adjthreshupr = 0.0 ;
			exhyp = 0.25 ;
			if (wscalc == 10)
				{
				hypp  =  50.0 * PENALSCALE ;
				hypp2 = 100.0 * PENALSCALE ;
				hypp3 =   0.0 ;
				hypp4 =  50.0 * PENALSCALE ;
				linepenalty = 0.10 ;
				}
			else if (wscalc == 11)
				{
				hypp  =  50.0 * PENALSCALE ;
				hypp2 =  30.0 * PENALSCALE ;
				hypp3 =   0.0 ;
				hypp4 =  50.0 * PENALSCALE ;
				linepenalty = 0.01 ;
				}
			else
				{
				hypp  =  25.0 * PENALSCALE ;
				hypp2 = 100.0 * PENALSCALE ;
				hypp3 =   0.0 ;
				hypp4 =  50.0 * PENALSCALE ;
				linepenalty = 0.01 ;
				}
			overrunpenalty = 0.0 ;
			overrunthreshold = 0.25 ;
			overrunmin = 0 ;
			lastlinestretch = 0 ;
			elppen = 0.0 ;
			}
		noscale = 0 ;
		}
}


void
caselastlinestretch(void)
{
	int n ;

	noscale = 1 ;
	dfact = 1 ;
	if (skip(0))
		lastlinestretch = 1 ;
	else
		{
		n = hatoi() ;
		if (!nonumb)
			lastlinestretch = n ;
		}
	if (lastlinestretch > 0)
		lastlinestretch = 1 ;
	else
		lastlinestretch = 0 ;
 	noscale = 0 ;
}


void
caseoverrunpenalty(void)
{
	float	n ;
	int	m ;

	if (skip(0))
		{
		overrunpenalty = 0.0 ;
		overrunthreshold = 0.25 ;
		overrunmin = 0 ;
		}
	else
		{
		n = atof() ;
		if (!nonumb)
			overrunpenalty = n / 100.0 ;
		if (!skip(0))
			{
			n = atof() ;
			if (!nonumb && n >= 1.0 && n <= 100.0)
				overrunthreshold = n / 100.0 ;
			}
		if (!skip(0))
			{
			dfact = EM ;
			m = max(hnumb(&m), 0) ;
			overrunmin = m ;
			dfact = 1 ;
			}
		}
}


void
caselinepenalty(void)
{
	float	n ;

	if (skip(0))
		linepenalty = 0.0 ;
	else
		{
		n = atof() ;
		if (!nonumb)
			linepenalty = n / 100.0 ;
		}
}


void
caselooseness(void)
{
	int	n ;

	if (skip(0))
		looseness = 0;
	else
		{
		n = hatoi() ;
		if (!nonumb)
			{
			if (n < -3)
				n = -3 ;
			else if (n > 3)
				n = 3 ;
			looseness = n ;
			}
		}
}


void
caseelppen(void)
{
	float	n ;

	if (skip(0))
		elppen = 0.0 ;
	else
		{
		n = atof() ;
		if (!nonumb)
			elppen = n / 100.0 ;
		}
}


void
caseadjpenalty(void)
{
	float	n ;

	if (!skip(0))
		{
		n = atof() ;
		if (!nonumb)
			adjpenalty = n / 100.0 ;
		if (!skip(0))
			{
			n = atof() ;
			if (!nonumb && n >= 0.1)
				adjthreshold = n / 100.0 ;
			if (!skip(0))
				{
				n = atof() ;
				if (!nonumb)
					{
					if (n > 1.0)
						adjthreshupr = n / 100.0 ;
					else
						adjthreshupr = 0.0 ;
					}
				}
			else
				adjthreshupr = 0.0 ;
			}
		}
}


void
caseadjlapenalty(void)
{
	float	n ;

	if (!skip(0))
		{
		n = atof() ;
		if (!nonumb)
			adjlapenalty = n / 100.0 ;

		if (!skip(0))
			{
			n = atof() ;
			if (!nonumb && n > 0.0)
				adjlathreshold = n / 100.0 ;
			}
		}
}


void
caseletcalc(void)
{
	int	n ;

	noscale = 1 ;
	if (!skip(0))
		{
		n = hatoi() ;
		if (!nonumb && n >= 0 && n <= 4)
			letcalc = n ;
		}
	noscale = 0 ;
}


void
caseletstren(void)
{
	float	s ;

	noscale = 1 ;
	if (!skip(0))
		{
		s = atof() ;
		if (!nonumb)
			{
			if (s < 0.0)
				s = 0.0 ;
			letstren = s / 100.0 ;
			}
		}
	else
		letstren = 1.0 ;
	noscale = 0 ;
}


void
caseletthresh(void)
{
	float	n ;

	noscale = 1 ;
	if (!skip(0))
		{
		n = atof() ;
		if (!nonumb)
			{
			if (n < 1.0)
				n = 1.0 ;
			if (n > 100.0)
				n = 100.0 ;
			letthreshlwr = n / 100.0 ;
			}
		if (!skip(0))
			{
			n = atof() ;
			if (!nonumb)
				{
				if (n < 100.0)
					n = 100.0 ;
				if (n > 200.0)
					n = 200.0 ;
				letthreshupr = n / 100.0 ;
				}
			}
		}
	noscale = 0 ;
}


void
caseletpen(void)
{
	int	n ;
	float	p ;

	noscale = 1 ;
	if (!skip(0))
		{
		n = hatoi() ;
		if (!nonumb)
			{
			if (n < 0)
				n = 0 ;
			letpen = n ;
			}
		if (!skip(0))
			{
			p = atof() ;
			if (!nonumb)
				letpenlwr = p / 100.0 ;
			}
		if (!skip(0))
			{
			p = atof() ;
			if (!nonumb)
				letpenupr = p / 100.0 ;
			}
		}
	noscale = 0 ;
}


void
caseexhyp(void)
{
	float	n ;

	if (skip(0))
		exhyp = 0 ;
	else
		{
		n = atof() ;
		if (!nonumb)
			exhyp = n / 100.0 ;
		}
}


void
caseletspc(void)
{
	int	n ;

	dfact = LAFACT / 100 ;
	noscale = 0 ;
	if (skip(1))
		goto ret ;
//	lower
	n = hatoi() ;
	if (!nonumb && n > 0)
		lspmin = LAFACT - n;
//	upper
	if (!skip(0))
		{
		dfact = LAFACT / 100;
		n = hatoi();
		if (!nonumb && n > 0)
			lspmax = n - LAFACT;
		}
ret:
	dfact = 1 ;
}


void
caseletshp(void)
{
	int	n ;

	dfact = LAFACT / 100 ;
	noscale = 0 ;
	if (skip(1))
		goto ret ;
//	shrink
	n = hatoi() ;
	if (!nonumb && n > 0)
		lshmin = LAFACT - n;
//	stretch
	if (!skip(0))
		{
		dfact = LAFACT / 100 ;
		n = hatoi();
		if (!nonumb && n > 0)
			lshmax = n - LAFACT;
		}
ret:
	dfact = 1 ;
}


void
caserhanglevel(void)
{
	int	n ;

	if (skip(0))
		rhanglevel = 0 ;
	else
		{
		n = hatoi() ;
		if (!nonumb && 0 <= n && n <= 2)
			rhanglevel = n ;
		}
}
