/*========================================================================*\

Copyright (c) 1993-1997  Paul Vojta

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
PAUL VOJTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

\*========================================================================*/

#ifndef lint
static	char	copyright[] =
"@(#) Copyright (c) 1993-1997 Paul Vojta.\n";
#endif

/*
 * Kpathsea version by Thomas Esser, John Interrante, Yves Arrouye, Karl Berry.
 */

#ifndef KPATHSEA

#include "config.h"
#include <ctype.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <fcntl.h>

#include <errno.h>
#ifndef errno
extern	int	errno;
#endif

#ifdef HAVE_DIRENT_H
#include <dirent.h>
typedef	struct dirent	struct_dirent;
#define NAMLEN(dirent) strlen((dirent)->d_name)
#else /* no <dirent.h> */
typedef	struct direct	struct_dirent;
#define NAMLEN(dirent) (dirent)->d_namlen
#ifdef HAVE_SYS_NDIR_H
#include <sys/ndir.h>
#endif
#ifdef HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#ifdef HAVE_NDIR_H
#include <ndir.h>
#endif
#endif /* no <dirent.h> */

#ifndef atof
double	atof();
#endif
char	*getenv();

#ifndef WIFSTOPPED
#define	WIFSTOPPED(status)	(((status) & 0377) == 0177)
#endif

#ifndef WIFSIGNALED
#define	WIFSIGNALED(status)	(((status) & 0377) != 0)
#endif

#ifndef WTERMSIG
#ifndef USE_UNION_WAIT
#define	WTERMSIG(status)	((status) & 0177)
#else
#define	WTERMSIG(status)	((status).w_termsig)
#endif
#endif

#ifndef WEXITSTATUS
#ifndef USE_UNION_WAIT
#define	WEXITSTATUS(status)	(((status) >> 8) & 0377)
#else
#define	WEXITSTATUS(status)	((status).w_retcode)
#endif
#endif

#ifndef SIGCHLD
#define	SIGCHLD	SIGCLD
#endif

/* How to open a binary file for reading:  */
#ifndef FOPEN_RBIN_MODE
#ifdef DOS
#define	FOPEN_RBIN_MODE "r+b"
#else
#if defined (VMS) || defined (VMCMS) || defined (OS2) || defined (WIN32)
#define	FOPEN_RBIN_MODE	"rb"
#else
#define	FOPEN_RBIN_MODE	"r"
#endif /* not (VM/CMS or VMS or OS2 or WIN32) */
#endif /* not DOS */
#endif /* not FOPEN_RBIN_MODE */

/* How to open a binary file for writing:  */
#ifndef FOPEN_WBIN_MODE
#ifdef DOS
#define	FOPEN_WBIN_MODE "w+b"
#else
#if defined (OS2) || defined (WIN32)
#define	FOPEN_WBIN_MODE "wb"
#else
#ifdef VMCMS
#define	FOPEN_WBIN_MODE "wb, lrecl=1024, recfm=f"
#else
#define	FOPEN_WBIN_MODE	"w"
#endif /* not VM/CMS */
#endif /* not (OS2 or WIN32) */
#endif /* not DOS */
#endif /* not FOPEN_WBIN_MODE */


/* These macros munge function declarations to make them work in both
   cases.  The P?H macros are used for declarations, the P?C for
   definitions.  Cf. <ansidecl.h> from the GNU C library.  P1H(void)
   also works for definitions of routines which take no args.  */

#if __STDC__

#define	AA(args) args /* For an arbitrary number; ARGS must be in parens.  */

#define	P1H(p1) (p1)

#define	P1C(t1,n1)(t1 n1)
#define	P2C(t1,n1, t2,n2)(t1 n1, t2 n2)
#define	P3C(t1,n1, t2,n2, t3,n3)(t1 n1, t2 n2, t3 n3)
#define	P4C(t1,n1, t2,n2, t3,n3, t4,n4)(t1 n1, t2 n2, t3 n3, t4 n4)
#define	P5C(t1,n1, t2,n2, t3,n3, t4,n4, t5,n5) \
  (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5)
#define	P6C(t1,n1, t2,n2, t3,n3, t4,n4, t5,n5, t6,n6) \
  (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6)
#define	P7C(t1,n1, t2,n2, t3,n3, t4,n4, t5,n5, t6,n6, t7,n7) \
  (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7)

#else /* not __STDC__ */

#define	AA(args) ()

#define	P1H(p1) ()

#define	P1C(t1,n1) (n1) t1 n1;
#define	P2C(t1,n1, t2,n2) (n1,n2) t1 n1; t2 n2;
#define	P3C(t1,n1, t2,n2, t3,n3) (n1,n2,n3) t1 n1; t2 n2; t3 n3;
#define	P4C(t1,n1, t2,n2, t3,n3, t4,n4) (n1,n2,n3,n4) \
  t1 n1; t2 n2; t3 n3; t4 n4;
#define	P5C(t1,n1, t2,n2, t3,n3, t4,n4, t5,n5) (n1,n2,n3,n4,n5) \
  t1 n1; t2 n2; t3 n3; t4 n4; t5 n5;
#define	P6C(t1,n1, t2,n2, t3,n3, t4,n4, t5,n5, t6,n6) (n1,n2,n3,n4,n5,n6) \
  t1 n1; t2 n2; t3 n3; t4 n4; t5 n5; t6 n6;
#define	P7C(t1,n1, t2,n2, t3,n3, t4,n4, t5,n5, t6,n6, t7,n7) \
  (n1,n2,n3,n4,n5,n6,n7) \
  t1 n1; t2 n2; t3 n3; t4 n4; t5 n5; t6 n6; t7 n7;
#define	const	/* nothing */

#endif /* not __STDC__ */

#else /* KPATHSEA */

#include <kpathsea/config.h>
#include <kpathsea/c-errno.h>
#include <kpathsea/c-ctype.h>
#include <kpathsea/c-fopen.h>
#include <kpathsea/c-pathmx.h>
#include <kpathsea/proginit.h>
#include <kpathsea/tex-file.h>
#include <kpathsea/tex-make.h>
#include <signal.h>
#if !defined(_AMIGA) && !defined(WIN32)
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <setjmp.h>

#ifndef HAVE_STRCHR
#define	strchr	index
#endif

#ifndef HAVE_STRRCHR
#define	strrchr	rindex
#endif

/* Add two new flags to kpathsea_debug for debugging gsftopk */
#define	GSPK_DEBUG_BITMAP	(KPSE_LAST_DEBUG + 1)
#define	GSPK_DEBUG_PK		(KPSE_LAST_DEBUG + 2)

#ifdef WIN32
#include <process.h>
#define	spawnlp	_spawnlp
#define	sleep	Sleep
#define	WIFSIGNALED(status)	(((status) & ((DWORD)0x0F0000000L)) != 0)
#define	WEXITSTATUS(status)	(((status) >> 8) & 0x0FF)
#else
#ifndef WIFSTOPPED
#define	WIFSTOPPED(status)	(((status) & 0377) == 0177)
#endif

#ifndef WIFSIGNALED
#define	WIFSIGNALED(status)	(((status) & 0377) != 0)
#endif

#ifndef WTERMSIG
#ifndef USE_UNION_WAIT
#define	WTERMSIG(status)	((status) & 0177)
#else
#define	WTERMSIG(status)	((status).w_termsig)
#endif
#endif

#ifndef WEXITSTATUS
#ifndef USE_UNION_WAIT
#define	WEXITSTATUS(status)	(((status) >> 8) & 0377)
#else
#define	WEXITSTATUS(status)	((status).w_retcode)
#endif
#endif

#endif /* WIN32 */

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_VFORK_H
#include <vfork.h>
#else
#define vfork fork
#endif

#endif /* KPATHSEA */

#if defined(HAVE_POLL) && !defined(HAVE_POLL_H)
#undef HAVE_POLL
#endif

#ifdef HAVE_POLL
#include <poll.h>
#endif

#ifdef _AMIGA
#include <proto/dos.h>
#include <dos/dostags.h>
#endif

#define	NUMBER(x)	(sizeof (x) / sizeof *(x))

/* if POSIX O_NONBLOCK is not available, use O_NDELAY */
#if !defined(O_NONBLOCK) && defined(O_NDELAY)
#define	O_NONBLOCK	O_NDELAY
#endif

#ifndef S_ISDIR
#if defined(S_IFMT) && defined(S_IFDIR)
#define	S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
#endif
#endif

#ifndef GS
#define	GS	"gs"
#endif

#ifdef __GNUC__
#define	NORETURN	volatile
#else
#define	NORETURN	/* nothing */
#endif

#if defined(I_STDARG) && __STDC__
#define	NeedVarargsPrototypes	1
#include <stdarg.h>
#else /* ! I_STDARG */
#define	NeedVarargsPrototypes	0
#include <varargs.h>
#endif /* ! I_STDARG */

typedef	char	Boolean;
#define	True	1
#define	False	0

#ifndef NeedWidePrototypes
#if __STDC__
#define	NeedWidePrototypes	1
#else
#define	NeedWidePrototypes	0
#endif
#endif /* NeedWidePrototypes */

#if NeedWidePrototypes
typedef	int		wide_bool;
#else
typedef	Boolean		wide_bool;
#endif

#ifndef MAXPATHLEN
#define	MAXPATHLEN	256
#endif

#define	PK_PRE	(char) 247
#define	PK_ID	(char) 89
#define	PK_SPC	(char) 240
#define	PK_POST	(char) 245
#define	PK_NOP	(char) 246

#ifndef KPATHSEA
char	progname[]	= "gsftopk ";
char	version[]	= "1.16";
#else
char	progname[]	= "gsftopk(k) ";
char	version[]	= "1.16";
#endif


/*
 *	Command line arguments
 */

#define		OPT_DBG		0x101

char		*mapline	= NULL;
const char	*mapfile	= NULL;
const char	*gspath		= GS;		/* gs interpreter path */
Boolean		dosnames	= False;
Boolean		quiet		= False;

struct option {
	const	char	*longname;
	short		shortname;
	Boolean		has_arg;
	void		*addr;
	int		value;
};

static	const	struct option	options[]	= {
		{"mapline",	0,	True,	&mapline, 0},
		{"mapfile",	0,	True,	&mapfile, 0},
		{"interpreter",	'i',	True,	&gspath, 0},
		{"dosnames",	0,	False,	&dosnames, True},
		{"quiet",	'q',	False,	&quiet,	True},
#ifdef KPATHSEA
		{"debug",	OPT_DBG,True,	NULL,	0},
#endif
		{"version",	'v',	False,	NULL,	0},
		{"help",	'h',	False,	NULL,	0}};

FILE		*pk_file	= NULL;
char		*xfilename;
int		col		= 0;		/* current column number */
const	char	*specinfo	= "";
int		gs_pid		= 0;

/*
 *	Reading from the pipe from ghostscript
 */

Boolean		data_eof	= False;

#ifndef _AMIGA

#define		BUFSIZE		512

typedef	unsigned char	byte;

int		data_fd;
byte		buffer[BUFSIZE];
byte		*data_out	= buffer;
byte		*data_end	= buffer;

#else /* _AMIGA */

FILE		*data_file;

/* This string will be used to open pipes in and out */
char	tmpname[]	= "gsftopkXXXXXX";

#endif /* _AMIGA */

/*
 *	Information from the .tfm file.
 */

int		tfm_lengths[12];
#define	lh	tfm_lengths[1]
#define	bc	tfm_lengths[2]
#define	ec	tfm_lengths[3]
#define	nw	tfm_lengths[4]

long		checksum;
long		design;
byte		width_index[256];
long		tfm_widths[256];

/*
 *	Information on the bitmap currently being worked on.
 */

byte		*bitmap;
int		width;
int		skip;
int		height;
int		hoff;
int		voff;
int		bytes_wide;
unsigned int	bm_size;
byte		*bitmap_end;
int		pk_len;


#ifndef _AMIGA
/*
 *	Exit, and kill the child process, too.
 */

NORETURN void
exit_toto_too P1H(void)
{
	if (gs_pid != 0)
#ifdef WIN32
	    TerminateProcess(gs_pid, 1);
#else
	    kill(gs_pid, SIGKILL);
#endif
	if (pk_file != NULL) {
	    fclose(pk_file);
	    if (unlink(xfilename) != 0) perror("unlink");
	}
	_exit(1);
}
#else
#define	exit_toto_too()	exit(1)
#endif

/*
 *	Print error message and quit.
 */

#if NeedVarargsPrototypes
NORETURN void
oops(const char *message, ...)
#else
/* VARARGS */
NORETURN void
oops(va_alist)
	va_dcl
#endif
{
#if !NeedVarargsPrototypes
	const char *message;
#endif
	va_list	args;

#if NeedVarargsPrototypes
	va_start(args, message);
#else
	va_start(args);
	message = va_arg(args, const char *);
#endif
	if (col != 0) putchar('\n');
	vfprintf(stderr, message, args);
	va_end(args);
	putc('\n', stderr);
	exit_toto_too();
}


/*
 *	Same as oops, but with arguments.
 */
#if NeedVarargsPrototypes
NORETURN void
opt_oops(const char *message, ...)
#else
/* VARARGS */
NORETURN void
opt_oops(va_alist)
	va_dcl
#endif
{
#if !NeedVarargsPrototypes
	const char *message;
#endif
	va_list	args;

#if NeedVarargsPrototypes
	va_start(args, message);
#else
	va_start(args);
	message = va_arg(args, const char *);
#endif
	fputs("gsftopk: ", stderr);
	vfprintf(stderr, message, args);
	va_end(args);
	fputs("\nTry `gsftopk --help' for more information.\n", stderr);
	exit(1);
}


#ifndef KPATHSEA

/*
 *	Either allocate storage or fail.
 */

static void *
xmalloc P1C(unsigned, size)
{
	void *mem = (void *) malloc(size);

	if (mem == NULL)
	    oops("gsftopk: Cannot allocate %u bytes.\n", size);
	return mem;
}

/*
 *	Either reallocate storage or fail.
 */

static void *
xrealloc P2C(char *, oldp, unsigned, size)
{
	void	*mem;

	mem = oldp == NULL ? (void *) malloc(size)
	    : (void *) realloc(oldp, size);
	if (mem == NULL)
	    oops("gsftopk: Cannot reallocate %u bytes.\n", size);
	return mem;
}

#endif /* not KPATHSEA */


#ifndef _AMIGA

/*
 *	Signal handlers.
 */

static	Boolean	got_sigchld	= False;

#ifdef WIN32
BOOL WINAPI
handle_sigterm(DWORD dwCtrlType)
{

	/*
	 *	Fix me:  There is a problem if a system() command is running.
	 *	We should wait for the child process to be interrupted.
	 *	Only way I can think of to do that : rewrite system() based on
	 *	spawn() with parsing of the command line and set a global pid
	 *	Next cwait(pid) in the HandlerRoutine.
	 */

	switch (dwCtrlType) {
	    case CTRL_C_EVENT:
	    case CTRL_BREAK_EVENT:
		fprintf(stderr, "...exiting\n");
		exit_toto_too();
		return FALSE;
	    default:
		fprintf(stderr, "... not exiting\n");
		return TRUE;
        }
}

#else /* ! WIN32 */

/* ARGSUSED */
static void
handle_sigchild P1C(int, signo)
{
	got_sigchld = True;
}

/* ARGSUSED */
static void
handle_sigterm P1C(int, signo)
{
	exit_toto_too();
}

#endif /* WIN32 */

#define	gs_is_done	(gs_pid == 0)

#ifdef USE_UNION_WAIT
typedef	union wait	gsf_wait_t;
#else
typedef	int		gsf_wait_t;
#endif

static void
wait_for_gs P1H(void)
{
	gsf_wait_t	status;

#ifdef _AMIGA

	/* Do nothing */

#elif defined(WIN32)

	if (_cwait( &status, gs_pid, _WAIT_CHILD ) == -1) {
	    perror("_cwait");
	    exit_toto_too();
	}

	/* FIXME: how to test if gs is stopped ? */

	gs_pid = 0;

	if (WIFSIGNALED(status))
	    oops("gs died due to signal %d\n", status);

	/* otherwise, it exited */
	if (WEXITSTATUS(status) != 0)
	    oops("gs terminated abnormally with status %d\n",
	      WEXITSTATUS(status));

#else /* neither _AMIGA nor WIN32 */

	got_sigchld = False;

	for (;;) {
	    if (wait(&status) != -1) break;
	    /* if (errno == EINTR) continue; */
	    if (errno != EINTR) {
		perror("wait");
		exit_toto_too();
	    }
	}

	if (WIFSTOPPED(status))
	    return;

	gs_pid = 0;

	if (WIFSIGNALED(status))
	    oops("gs died due to signal %d\n", WTERMSIG(status));

	/* otherwise, it exited */
	if (WEXITSTATUS(status) != 0)
	    oops("gs terminated abnormally with status %d\n",
	      WEXITSTATUS(status));

#endif /* neither _AMIGA nor WIN32 */

}

#endif /* ! _AMIGA */


/*
 *	Routines to read from the data file.
 */

#ifdef _AMIGA

#define	data_getc()	getc(data_file)
#define	data_ungetc(c)	ungetc(c, data_file)
#define	data_read(p, l)	fread(p, 1, l, data_file)

#else /* not _AMIGA */

#ifdef HAVE_POLL
#define	ISSET(a, b)		(poll_fd.revents & POLLIN != 0)
#else
#define	ISSET(a, b)		FD_ISSET(a, b)
#endif

/* Some aix's (e.g. 3.2.5) need this to declare fd_set, others don't
	This is a horrible kludge and should be replaced by something better.
	P. Breitenlohner, 28 Jan 1998 */
#ifdef _AIX
#ifndef HAVE_POLL
#include <sys/select.h>
#endif
#endif

static void
data_fillbuf P1H(void)
{
	int			n;

#ifndef WIN32
#ifdef HAVE_POLL
	static	struct pollfd	poll_fd	= {0, POLLIN, 0};
#else
	fd_set			read_fds;
	struct timeval		timeout;
#endif
#endif /* ! WIN32 */

	if (data_eof)
	    return;

#ifndef WIN32	/* Win32 doesn't recognize fd's as sockets */
	/* wait for readable data */
	if (!gs_is_done) {
	    for (;;) {
		if (!got_sigchld) {
#ifdef HAVE_POLL
		    poll_fd.fd = data_fd;
		    poll_fd.revents = 0;
		    if (poll(&poll_fd, 1, 5000) == -1) {
			if (errno != EINTR) {
			    perror("poll");
			    sleep(4);
			}
			continue;
		    }
#else
		    FD_ZERO(&read_fds);
		    FD_SET(data_fd, &read_fds);
		    timeout.tv_sec = 5;
		    timeout.tv_usec = 0;
		    if (select(data_fd + 1, &read_fds, (fd_set *) NULL,
		      (fd_set *) NULL, &timeout) == -1) {
			if (errno != EINTR) {
			    perror("select");
			    sleep(4);
			}
			continue;
		    }
#endif
		}
		if (got_sigchld) {
		    wait_for_gs();
		    break;
		}
		if (ISSET(data_fd, &read_fds))
		    break;
	    }
	}

#endif /* ! WIN32 */

	/* read the data */
	for (;;) {
	    n = read(data_fd, (void *) (data_out = buffer), BUFSIZE);
	    if (n >= 0)
		break;
	    if (errno == EINTR)
		continue;
	    if (gs_is_done && errno == EAGAIN) {
		n = 0;
		break;
	    }
	    perror("read from gs");
	    sleep(4);
	}

	data_end = data_out + n;
	if (n == 0)
	    data_eof = True;
}

static byte
data_fgetc P1H(void)
{
	if (data_out >= data_end)
	    data_fillbuf();

	return data_eof ? EOF : *data_out++;
}

#define	data_getc()	(data_out < data_end ? *data_out++ : data_fgetc())

static void
data_ungetc P1C(byte, c)
{
	if (data_out <= buffer)
	    oops("Too many calls to data_ungetc()");

	*--data_out = c;
}

static int
data_read P2C(byte *, buf, int, n)
{
	byte	*buf1	= buf;
	byte	*buf_end = buf + n;
	int	n1;

	if (buf1 >= buf_end)
	    return 0;

	while (!data_eof) {
	    n1 = buf_end - buf1;
	    if (n1 > data_end - data_out)
		n1 = data_end - data_out;
	    memcpy(buf1, data_out, n1);
	    buf1 += n1;
	    data_out += n1;
	    if (buf1 >= buf_end) break;
	    data_fillbuf();
	}

	return buf1 - buf;
}

static void
data_gets P2C(byte *, buf, int, n)
{
	byte	*buf1	= buf;
	byte	*buf_end = buf + n - 1;
	int	n1;
	byte	*p1;

	if (n <= 0)
	    return;

	for (;;) {
	    if (data_eof)
		oops("Premature end of file");
	    n1 = buf_end - buf1;
	    if (n1 > data_end - data_out)
		n1 = data_end - data_out;
	    p1 = (byte *) memchr((char *) data_out, '\n', n1);
	    if (p1 != NULL)
		n1 = p1 + 1 - data_out;
	    memcpy((char *) buf1, (char *) data_out, n1);
	    buf1 += n1;
	    data_out += n1;
	    if (p1 != NULL || buf1 >= buf_end) break;
	    data_fillbuf();
	}

	*buf1 = '\0';

	return;
}

#endif /* not _AMIGA */


/*
 *	Here's the patch searching stuff.  First the typedefs and variables.
 */

#ifndef KPATHSEA
static	char	searchpath[MAXPATHLEN + 1];
#else
static	char	*searchpath;
#endif

#define	HUNKSIZE	(MAXPATHLEN + 2)

struct spacenode {	/* used for storage of directory names */
	struct spacenode	*next;
	char			*sp_end;	/* end of data for this chunk */
	char			sp[HUNKSIZE];
}
	firstnode;

#ifndef KPATHSEA
static	jmp_buf		found_env;
static	FILE		*searchfile;
static	const	char	*searchname;
static	int		searchnamelen;

static const char *
find_dbl_slash P2C(const char *, sp_bgn, const char *, sp_end)
{
	const	char *p;

	for (;;) {
	    p = memchr(sp_bgn, '/', sp_end - sp_bgn);
	    if (p == NULL) return sp_end;
	    if (p[1] == '/') return p;
	    sp_bgn = p + 1;
	}
}

static void
main_search_proc P7C(char *, matpos, const char *, sp_pos,
	const char *, sp_slash, const char *, sp_end,
	wide_bool, skip_subdirs, struct spacenode *, space, char *, spacenext)
{
	char		*mp;
	struct stat	statbuf;
	DIR		*dir;
	struct_dirent	*entry;
	int		lenleft;
	int		len;
	struct spacenode *space1;
	char		*spacenext1;

	mp = matpos + (sp_slash - sp_pos);
	/* check length */
	if (mp + searchnamelen >= searchpath + sizeof(searchpath) - 2) return;
	memcpy(matpos, sp_pos, sp_slash - sp_pos);
	if (sp_slash == sp_end) {	/* try for a file */
	    *mp = '/';
	    strcpy(mp + (mp == searchpath || mp[-1] != '/'), searchname);
	    searchfile = fopen(searchpath, "r");
	    if (searchfile != NULL) longjmp(found_env, True);
	}
	else {/* try for a subdirectory */
	    *mp = '\0';
	    if (stat(searchpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
		*mp++ = '/';
		main_search_proc(mp, sp_slash + 2,
		    find_dbl_slash(sp_slash + 2, sp_end), sp_end,
		    statbuf.st_nlink <= 2, space, spacenext);
	    }
	}
	if (skip_subdirs) return;
	*matpos = '\0';
	dir = opendir(searchpath);
	if (dir == NULL) return;
	lenleft = searchpath + sizeof(searchpath) - matpos;
	space1 = space;
	spacenext1 = spacenext;
	for (;;) {
	    entry = readdir(dir);
	    if (entry == NULL) break;
	    len = NAMLEN(entry) + 1;
	    if (len > lenleft) continue;	/* too long */
	    strcpy(matpos, entry->d_name);
	    if (*matpos == '.' && (matpos[1] == '\0' || (matpos[1] == '.'
		    && matpos[2] == '\0')))
		continue;		/* ignore . and .. */
	    if (stat(searchpath, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
		continue;		/* if not a directory */
	    if (statbuf.st_nlink > 2) ++len;
	    if (spacenext1 + len > space1->sp + HUNKSIZE) {
		space1->sp_end = spacenext1;
		if (space1->next == NULL) {
		    space1->next = xmalloc(sizeof(struct spacenode));
		    space1->next->next = NULL;
		}
		space1 = space1->next;
		spacenext1 = space1->sp;
	    }
	    if (statbuf.st_nlink > 2) {
		*spacenext1++ = '/';
		--len;
	    }
	    memcpy(spacenext1, entry->d_name, len - 1);
	    spacenext1[len - 1] = '\0';
	    spacenext1 += len;
	}
	closedir(dir);
	for (;;) {
	    space1->sp_end = spacenext1;
	    if (spacenext == space->sp_end) {
		if (space == space1) break;
		space = space->next;
		spacenext = space->sp;
	    }
	    skip_subdirs = True;
	    if (*spacenext == '/') {
		++spacenext;
		skip_subdirs = False;
	    }
	    len = strlen(spacenext);
	    memcpy(matpos, spacenext, len);
	    matpos[len] = '/';
	    main_search_proc(matpos + len + 1, sp_pos, sp_slash, sp_end,
		skip_subdirs, space1, spacenext1);
	    spacenext += len + 1;
	}
}

static FILE *
search P3C(const char *, path, const char *, path_var, const char *, name)
{
	const	char	*env_path	= NULL;
	FILE		*f;

	if (path_var != NULL) {
	    if (*name == '/') {
		strcpy(searchpath, name);
		return fopen(searchpath, "r");
	    }
	    env_path = getenv(path_var);
	}
	if (env_path == NULL) {
	    env_path = path;
	    path = NULL;
	}
	searchname = name;
	searchnamelen = strlen(name);
	for (;;) {
	    const char *p;

	    p = strchr(env_path, ':');
	    if (p == NULL) p = env_path + strlen(env_path);
	    if (p == env_path) {
		if (path != NULL) {
		    f = search(path, (char *) NULL, name);
		    if (f != NULL) return f;
		}
	    }
	    else {
		if (setjmp(found_env))
		    return searchfile;
		main_search_proc(searchpath,
		    env_path, find_dbl_slash(env_path, p), p,
		    True, &firstnode, firstnode.sp);
	    }
	    if (*p == '\0') return NULL;
	    env_path = p + 1;
	}
}

#endif /* ! KPATHSEA */

/*
 *	Add to dlstring
 */

char		*dlstring	= NULL;
unsigned int	dls_len		= 0;
unsigned int	dls_max		= 0;

void
addtodls P1C(const char *, s)
{
	int	len	= strlen(s);

	if (dls_len + len >= dls_max) {
	    unsigned int newsize = dls_max + 80;

	    if (newsize <= dls_len + len) newsize = dls_len + len + 1;
	    dlstring = xrealloc(dlstring, dls_max = newsize);
	}
	strcpy(dlstring + dls_len, s);
	dls_len += len;
}



long
getlong P1C(FILE *, f)
{
	int	value;

	value = (int) ((byte) getc(f)) << 24;
	value |= (int) ((byte) getc(f)) << 16;
	value |= (int) ((byte) getc(f)) << 8;
	value |= (int) ((byte) getc(f));
	return value;
}


char	line[82];

void
expect P1C(const char *, waitingfor)
{
	for (;;) {
#ifndef _AMIGA
	    data_gets((byte *) line, sizeof(line));
#else
	    if (fgets(line, sizeof(line), data_file) == NULL)
		oops("Premature end of file");
#endif
	    if (memcmp(line, waitingfor, strlen(waitingfor)) == 0) return;
	    fputs("gs: ", stdout);
	    for (;;) {
		fputs(line, stdout);
		if (*line == '\0' || line[strlen(line) - 1] == '\n') break;
#ifndef _AMIGA
		data_gets((byte *) line, sizeof(line));
#else
		if (fgets(line, sizeof(line), data_file) == NULL)
		    oops("Premature end of file");
#endif
	    }
	}
}

void
whitespace P1H(void)
{
	char	c;

	for (;;) {
	    c = data_getc();
	    if (c == '#')
		do c = data_getc(); while (!data_eof && c != '\n');
	    else if (!isspace(c)) {
		data_ungetc(c);
		break;
	    }
	}
}

int
getint P1H(void)
{
	char	c;
	int	i	= 0;

	do c = data_getc(); while (isspace(c));
	if (c < '0' || c > '9') oops("digit expected");
	do {
	    i = i * 10 + (c - '0');
	    c = data_getc();
	} while (c >= '0' && c <= '9');
	if (!data_eof) data_ungetc(c);
	return i;
}

static	byte	masks[]	= {0, 1, 3, 7, 017, 037, 077, 0177, 0377};

byte	flag;
int	pk_dyn_f;
int	pk_dyn_g;
int	base;		/* cost of this character if pk_dyn_f = 0 */
int	deltas[13];	/* cost of increasing pk_dyn_f from i to i+1 */

/*
 *	Add up statistics for putting out the given shift count
 */

static void
tallyup P1C(int, n)
{
	int	m;

	if (n > 208) {
	    ++base;
	    n -= 192;
	    for (m = 0x100; m != 0 && m < n; m <<= 4) base += 2;
	    if (m != 0 && (m = (m - n) / 15) < 13) deltas[m] += 2;
	}
	else if (n > 13) ++deltas[(208 - n) / 15];
	else --deltas[n - 1];
}

/*
 *	Routines for storing the shift counts
 */

static	Boolean	odd	= False;
static	byte	part;

static void
pk_put_nyb P1C(int, n)
{
	if (odd) {
	    *bitmap_end++ = (part << 4) | n;
	    odd = False;
	}
	else {
	    part = n;
	    odd = True;
	}
}

static void
pk_put_long P1C(int, n)
{
	if (n >= 16) {
	    pk_put_nyb(0);
	    pk_put_long(n / 16);
	}
	pk_put_nyb(n % 16);
}

static void
pk_put_count P1C(int, n)
{
	if (n > pk_dyn_f) {
	    if (n > pk_dyn_g)
		pk_put_long(n - pk_dyn_g + 15);
	    else {
		pk_put_nyb(pk_dyn_f + (n - pk_dyn_f + 15) / 16);
		pk_put_nyb((n - pk_dyn_f - 1) % 16);
	    }
	}
	else pk_put_nyb(n);
}

static void
trim_bitmap P1H(void)
{
	byte	*p;
	byte	mask;

	/* clear out garbage bits in bitmap */
	if (width % 8 != 0) {
	    mask = ~masks[8 - width % 8];
	    for (p = bitmap + bytes_wide - 1; p < bitmap_end; p += bytes_wide)
		*p &= mask;
	}

	/*
	 *	Find the bounding box of the bitmap.
	 */

	/* trim top */
	skip = 0;
	mask = 0;
	for (;;) {
	    if (bitmap >= bitmap_end) {	/* if bitmap is empty */
		width = height = hoff = voff = 0;
		return;
	    }
	    p = bitmap + bytes_wide;
	    while (p > bitmap) mask |= *--p;
	    if (mask) break;
	    ++skip;
	    bitmap += bytes_wide;
	}
	height -= skip;
	voff -= skip;
#ifdef DEBUG
#ifdef KPATHSEA
     if (KPSE_DEBUG_P (GSPK_DEBUG_PK))
#endif
	if (skip < 2 || skip > 3)
	    printf("Character has %d empty rows at top\n", skip);
#endif

	/* trim bottom */
	skip = 0;
	mask = 0;
	for (;;) {
	    p = bitmap_end - bytes_wide;
	    while (p < bitmap_end) mask |= *p++;
	    if (mask) break;
	    ++skip;
	    bitmap_end -= bytes_wide;
	}
	height -= skip;
#ifdef DEBUG
#ifdef KPATHSEA
     if (KPSE_DEBUG_P (GSPK_DEBUG_PK))
#endif
	if (skip < 2 || skip > 3)
	    printf("Character has %d empty rows at bottom\n", skip);
#endif

	/* trim right */
	skip = 0;
	--width;
	for (;;) {
	    mask = 0;
	    for (p = bitmap + width / 8; p < bitmap_end; p += bytes_wide)
		mask |= *p;
	    if (mask & (0x80 >> (width % 8))) break;
	    --width;
	    ++skip;
	}
	++width;
#ifdef DEBUG
#ifdef KPATHSEA
     if (KPSE_DEBUG_P (GSPK_DEBUG_PK))
#endif
	if (skip < 2 || skip > 3)
	    printf("Character has %d empty columns at right\n", skip);
#endif

	/* trim left */
	skip = 0;
	for (;;) {
	    mask = 0;
	    for (p = bitmap + skip / 8; p < bitmap_end; p += bytes_wide)
		mask |= *p;
	    if (mask & (0x80 >> (skip % 8))) break;
	    ++skip;
	}
	width -= skip;
	hoff -= skip;
#ifdef DEBUG
#ifdef KPATHSEA
     if (KPSE_DEBUG_P (GSPK_DEBUG_PK))
#endif
	if (skip < 2 || skip > 3)
	    printf("Character has %d empty columns at left\n", skip);
#endif
	bitmap += skip / 8;
	skip = skip % 8;
}

/*
 *	Pack the bitmap using the rll method.  (Return false if it's better
 *	to just pack the bits.)
 */

static Boolean
pk_rll_cvt P1H(void)
{
	static	int	*counts		= NULL;	/* area for saving bit counts */
	static	int	maxcounts	= 0;	/* size of this area */
	unsigned int	ncounts;		/* max to allow this time */
	int	*nextcount;			/* next count value */
	int	*counts_end;			/* pointer to end */
	byte	*rowptr;
	byte	*p;
	byte	mask;
	byte	*rowdup;			/* last row checked for dup */
	byte	paint_switch;			/* 0 or 0xff */
	int	bits_left;			/* bits left in row */
	int	cost;
	int	i;

	/*
	 *	Allocate space for bit counts.
	 */

	ncounts = (width * height + 3) / 4;
	if (ncounts > maxcounts) {
	    if (counts != NULL) free(counts);
	    counts = xmalloc((ncounts + 2) * sizeof(int));
	    maxcounts = ncounts;
	}
	counts_end = counts + ncounts;

	/*
	 *	Form bit counts and collect statistics
	 */
	base = 0;
	bzero(deltas, sizeof(deltas));
	rowdup = NULL;	/* last row checked for duplicates */
	p = rowptr = bitmap;
	mask = 0x80 >> skip;
	flag = 0;
	paint_switch = 0;
	if (*p & mask) {
	    flag = 8;
	    paint_switch = 0xff;
	}
	bits_left = width;
	nextcount = counts;
	while (rowptr < bitmap_end) {	/* loop over shift counts */
	    int shift_count = bits_left;

	    for (;;) {
		if (bits_left == 0) {
		    if ((p = rowptr += bytes_wide) >= bitmap_end) break;
		    mask = 0x80 >> skip;
		    bits_left = width;
		    shift_count += width;
		}
		if (((*p ^ paint_switch) & mask) != 0) break;
		--bits_left;
		mask >>= 1;
		if (mask == 0) {
		    ++p;
		    while (*p == paint_switch && bits_left >= 8) {
			++p;
			bits_left -= 8;
		    }
		    mask = 0x80;
		}
	    }
	    if (nextcount >= counts_end) return False;
	    shift_count -= bits_left;
	    *nextcount++ = shift_count;
	    tallyup(shift_count);
	    /* check for duplicate rows */
	    if (rowptr != rowdup && bits_left != width) {
		byte	*p1	= rowptr;
		byte	*q	= rowptr + bytes_wide;
		int	repeat_count;

		while (q < bitmap_end && *p1 == *q) ++p1, ++q;
		repeat_count = (p1 - rowptr) / bytes_wide;
		if (repeat_count > 0) {
		    *nextcount++ = -repeat_count;
		    if (repeat_count == 1) --base;
		    else {
			++base;
			tallyup(repeat_count);
		    }
		    rowptr += repeat_count * bytes_wide;
		}
		rowdup = rowptr;
	    }
	    paint_switch = ~paint_switch;
	}

#ifdef DEBUG
#ifdef KPATHSEA
	if (KPSE_DEBUG_P (GSPK_DEBUG_BITMAP))
#endif
	{
	    /*
	     *	Dump the bitmap
	     */

	    for (p = bitmap; p < bitmap_end; p += bytes_wide) {
		byte *p1	= p;
		int j;

		mask = 0x80 >> skip;
		for (j = 0; j < width; ++j) {
		    putchar(*p1 & mask ? '@' : '.');
		    if ((mask >>= 1) == 0) mask = 0x80, ++p1;
		}
		putchar('\n');
	    }
	    putchar('\n');
	}
#endif

	/*
	 *	Determine the best pk_dyn_f
	 */

	pk_dyn_f = 0;
	cost = base += 2 * (nextcount - counts);
	for (i = 1; i < 14; ++i) {
	    base += deltas[i - 1];
	    if (base < cost) {
		pk_dyn_f = i;
		cost = base;
	    }
	}
	/* last chance to bail out */
	if (cost * 4 > width * height) return False;

	/*
	 *	Pack the bit counts
	 */

	pk_dyn_g = 208 - 15 * pk_dyn_f;
	flag |= pk_dyn_f << 4;
	bitmap_end = bitmap;
	*nextcount = 0;
	nextcount = counts;
	while (*nextcount != 0) {
	    if (*nextcount > 0) pk_put_count(*nextcount);
	    else
		if (*nextcount == -1) pk_put_nyb(15);
		else {
		    pk_put_nyb(14);
		    pk_put_count(-*nextcount);
		}
	    ++nextcount;
	}
	if (odd) {
	    pk_put_nyb(0);
	    ++cost;
	}
	if (cost != 2 * (bitmap_end - bitmap))
	    printf("Cost miscalculation:  expected %d, got %d\n", cost,
		2 * (bitmap_end - bitmap));
	pk_len = bitmap_end - bitmap;
	return True;
}

static void
pk_bm_cvt P1H(void)
{
	byte	*rowptr;
	byte	*p;
	int	blib1;		/* bits left in byte */
	int	bits_left;	/* bits left in row */
	byte	*q;
	int	blib2;
	byte	nextbyte;

	flag = 14 << 4;
	q = bitmap;
	blib2 = 8;
	nextbyte = 0;
	for (rowptr = bitmap; rowptr < bitmap_end; rowptr += bytes_wide) {
	    p = rowptr;
	    blib1 = 8 - skip;
	    bits_left = width;
	    if (blib2 != 8) {
		int	n;

		if (blib1 < blib2) {
		    nextbyte |= *p << (blib2 - blib1);
		    n = blib1;
		}
		else {
		    nextbyte |= *p >> (blib1 - blib2);
		    n = blib2;
		}
		blib2 -= n;
		if ((bits_left -= n) < 0) {
		    blib2 -= bits_left;
		    continue;
		}
		if ((blib1 -= n) == 0) {
		    blib1 = 8;
		    ++p;
		    if (blib2 > 0) {
			nextbyte |= *p >> (8 - blib2);
			blib1 -= blib2;
			bits_left -= blib2;
			if (bits_left < 0) {
			    blib2 = -bits_left;
			    continue;
			}
		    }
		}
		*q++ = nextbyte;
	    }
	    /* fill up whole (destination) bytes */
	    while (bits_left >= 8) {
		nextbyte = *p++ << (8 - blib1);
		*q++ = nextbyte | (*p >> blib1);
		bits_left -= 8;
	    }
	    /* now do the remainder */
	    nextbyte = *p << (8 - blib1);
	    if (bits_left > blib1) nextbyte |= p[1] >> blib1;
	    blib2 = 8 - bits_left;
	}
	if (blib2 != 8) *q++ = nextbyte;
	pk_len = q - bitmap;
}

static void
putshort P1C(short, w)
{
	putc(w >> 8, pk_file);
	putc(w, pk_file);
}

static void
putmed P1C(long, w)
{
	putc(w >> 16, pk_file);
	putc(w >> 8, pk_file);
	putc(w, pk_file);
}

static void
putlong P1C(long, w)
{
	putc(w >> 24, pk_file);
	putc(w >> 16, pk_file);
	putc(w >> 8, pk_file);
	putc(w, pk_file);
}

static void
putglyph P1C(int, cc)
{
	static	Boolean	have_first_line = False;
	static	int	llx, lly, urx, ury;
	static	float	char_width;
	static	byte	*area1	= NULL;
	static unsigned int size1 = 0;
	static	int	i;
	long	dm;
	long	tfm_wid;
	byte	*p;

	if (!quiet) {
	    int wid;
	    static const char *s = "";

	    wid = (cc >= 100) + (cc >= 10) + 4;
	    if (col + wid > 80) {
		s = "\n";
		col = 0;
	    }
	    printf("%s[%d", s, cc);
	    fflush(stdout);
	    col += wid;
	    s = " ";
	}
	if (!have_first_line) {
	    expect("#^");
	    if (sscanf(line, "#^ %d %d %d %d %d %f\n", &i,
		    &llx, &lly, &urx, &ury, &char_width) != 6)
		oops("Cannot scanf first line");
	}
	if (i < cc) oops("Character %d received, %d expected", i, cc);
	if (i > cc) {
	    fprintf(stderr, "Character %d is missing.\n", cc);
	    have_first_line = True;
	    return;
	}
	have_first_line = False;
	hoff = -llx + 2;
	voff = ury + 2 - 1;
	expect("P4\n");
	whitespace();
	width = getint();
	whitespace();
	height = getint();
	(void) data_getc();
	if (width != urx - llx + 4 || height != ury - lly + 4)
	    oops("Dimensions do not match:  %d %d %d %d %d %d",
		llx, lly, urx, ury, width, height);
	bytes_wide = (width + 7) / 8;
	bm_size = bytes_wide * height;
	if (size1 < bm_size) {
	    if (area1 != NULL) free(area1);
	    area1 = xmalloc(bm_size);
	    size1 = bm_size;
	}
	for (p = area1 + (height - 1) * bytes_wide; p >= area1; p -= bytes_wide)
	    if (data_read(p, bytes_wide) != bytes_wide)
		oops("Cannot read bitmap of size %u", bm_size);
	bitmap = area1;
	bitmap_end = bitmap + bm_size;
	trim_bitmap();
	if (height == 0 || !pk_rll_cvt()) pk_bm_cvt();
	tfm_wid = tfm_widths[width_index[cc]];
	dm = (long) (char_width + 0.5) - (char_width < -0.5);
	if (pk_len + 8 < 4 * 256 && tfm_wid < (1<<24) &&
		dm >= 0 && dm < 256 && width < 256 && height < 256 &&
		hoff >= -128 && hoff < 128 && voff >= -128 && voff < 128) {
	    putc(flag | ((pk_len + 8) >> 8), pk_file);
	    putc(pk_len + 8, pk_file);
	    putc(cc, pk_file);
	    putmed(tfm_wid);
	    putc(dm, pk_file);
	    putc(width, pk_file);
	    putc(height, pk_file);
	    putc(hoff, pk_file);
	    putc(voff, pk_file);
	} else
	if (pk_len + 13 < 3 * 65536L && tfm_wid < (1<<24) &&
		dm >= 0 && dm < 65536L && width < 65536L && height < 65536L &&
		hoff >= -65536L && hoff < 65536L &&
		voff >= -65536L && voff < 65536L) {
	    putc(flag | 4 | ((pk_len + 13) >> 16), pk_file);
	    putshort(pk_len + 13);
	    putc(cc, pk_file);
	    putmed(tfm_wid);
	    putshort(dm);
	    putshort(width);
	    putshort(height);
	    putshort(hoff);
	    putshort(voff);
	}
	else {
	    putc(flag | 7, pk_file);
	    putlong(pk_len + 28);
	    putlong(cc);
	    putlong(tfm_wid);
	    putlong((long) (char_width * 65536.0 + 0.5) - (char_width < -0.5));
	    putlong(0);
	    putlong(width);
	    putlong(height);
	    putlong(hoff);
	    putlong(voff);
	}
	fwrite(bitmap, 1, pk_len, pk_file);
	if (!quiet) {
	    putchar(']');
	    fflush(stdout);
	}
}

static void
putspecl P2C(const char *, str1, const char *, str2)
{
	int	len1	= strlen(str1);
	int	len2	= 0;

	if (str2 != NULL) len2 = strlen(str2);
	if (len1 + len2 > 255) return;
	putc(PK_SPC, pk_file);
	putc(len1 + len2, pk_file);
	fwrite(str1, 1, len1, pk_file);
	if (len2 != 0) fwrite(str2, 1, len2, pk_file);
}

#ifdef WIN32
char *
quotearg(char *s)
{
	char	*p;
	int	len;

	/* Remove leading blanks */
	while (ISSPACE(*s)) s++;

	/* remove trailing blanks */
	for(len = strlen(s); len > 0 && ISSPACE(s[len-1]); len--);

	/* Is there some space between s and s+len ? */
	if (strchr(s, ' ') < s + len) {
	    p = xmalloc(len + 3);
	    strcpy(p + 1, s);
	    *p = '"';
	    p[strlen(s) + 1] = '"';
	    p[strlen(s) + 2] = '\0';
	}
	else {
	    /* just in case we have removed blank chars */
	    p = xmalloc(len+1);
	    strncpy(p, s, len);
	    p[len] = '\0';
	}
	return p;
}
#endif /* WIN32 */


int
main P2C(int, argc, char **, argv)
{
	FILE		*render_ps;
	FILE		*tfm_file;
	char		**argp;
	float		dpi;
	char		*fontname;
	int		fontlen;
	unsigned int	maplinelen;
	char		*p;
	char		*PSname		= NULL;
	char		*specinf	= NULL;
	char		*specp		= NULL;	/* NULL pacifies lint */
	char		charlist[10*2 + 90*3 + 156*4 + 1];
	char		designstr[20];
	char		dpistr[20];
#ifdef HAVE_SIGACTION
	struct sigaction sigact;
#endif
#ifdef _AMIGA
	char		fngs[50];
	char		fngsf[50];
	char		tfm_path[256];
	BPTR		in, out;
#else
	int		std_out[2];
#endif
	int		std_in[2];
#ifdef WIN32
	int		status;
#endif
	int		cc;
	int		ppp;
	int		i;
#ifdef WIN32
	int		oldin, oldout;
	char		gs_absolute_path[PATH_MAX];
	char		*gs_relative_path;
#endif

	argp = argv;
	while (++argp < argv + argc && (*argp)[0] == '-') {
	    const struct option *opt_ptr;
	    const struct option *opt;
	    char *arg = *argp + 1;

	    if (*arg == '\0') --arg;	/* this will flag an error later */
	    if (*arg != '-') {		/* if short argument */
		opt = options;
		for (;;) {
		    if (*arg == opt->shortname)
			break;
		    if (++opt >= options + NUMBER(options))
			opt_oops("invalid option -- %c", *arg);
		}
		if (opt->has_arg) {
		    ++arg;
		    if (*arg == '\0') {
			if (++argp >= argv + argc)
			    opt_oops("option requires an argument -- %c",
			      arg[-1]);
			arg = *argp;
		    }
		}
		else {
		    if (arg[1] != '\0')
			opt_oops("invalid number of bytes in option `%s'",
			  arg - 1);
		}
	    }
	    else {			/* long argument */
		int	len;
		char	*arg1;

		++arg;
		if (*arg == '\0') {	/* if -- */
		    ++argp;
		    break;
		}
		len = strlen(arg);
		arg1 = memchr(arg, '=', len);
		if (arg1 != NULL) {
		    len = arg1 - arg;
		    ++arg1;
		}
		opt = NULL;
		for (opt_ptr = options; opt_ptr < options + NUMBER(options);
		  ++opt_ptr)
		    if (memcmp(arg, opt_ptr->longname, len) == 0) {
			if (opt != NULL)
			    opt_oops("option `%s' is ambiguous.", arg - 2);
			opt = opt_ptr;
		    }
		if (opt == NULL)
		    opt_oops("unrecognized option `%s'", arg - 2);
		if (opt->has_arg) {
		    if (arg1 == NULL) {
			if (++argp >= argv + argc)
			    opt_oops("option `--%s' requires an argument.",
			      opt->longname);
			arg1 = *argp;
		    }
		}
		else {
		    if (arg1 != NULL)
			opt_oops("option `--%s' doesn't allow an argument.",
			  opt->longname);
		}
		arg = arg1;
	    }		/* end long argument */

	    if (opt->addr != NULL)
		if (opt->has_arg)
		    *((char **) opt->addr) = arg;
		else
		    *((Boolean *) opt->addr) = opt->value;

	    switch (opt->shortname) {
#ifdef KPATHSEA
	    case OPT_DBG:
		kpathsea_debug |= atoi(arg);
#endif
	    case 'h':
#ifndef KPATHSEA
		puts("\
Usage:  gsftopk [OPTION] FONT DPI\n\
Translate the PostScript Type 1 font FONT to PK bitmap format at DPI dpi.\n\
\n\
  --mapline=LINE	use LINE as the line from the .map file.\n\
  --mapfile=FILE	use FILE as a .map file; default psfonts.map.\n\
  -i GS, --interpreter=GS  use GS as Ghostscript interpreter.\n\
  --dosnames		short pk filename (cmr10.pk instead of cmr10.600pk).\n\
  -q, --quiet		don't print progress information to standard output.\n\
  -h, --help		print this message and exit.\n\
  -v, --version		print version number and exit.\n");
#else
		puts("\
Usage:  gsftopk [OPTION] FONT DPI\n\
Translate the PostScript Type 1 font FONT to PK bitmap format at DPI dpi.\n\
\n\
  --mapline=LINE	use LINE as the line from the .map file.\n\
  --mapfile=FILE	use FILE as a .map file; default psfonts.map.\n\
  -i GS, --interpreter=GS  use GS as Ghostscript interpreter.\n\
  --dosnames		short pk filename (cmr10.pk instead of cmr10.600pk).\n\
  -q, --quiet		don't print progress information to standard output.\n\
  --debug=NUM		set debugging flags.\n\
  -h, --help		print this message and exit.\n\
  -v, --version		print version number and exit.\n");
#endif
		return 0;
	    case 'v':
#ifndef KPATHSEA
		printf("gsftopk %s\n", version);
#else
		{
		    extern DllImport char *kpathsea_version_string;

		    printf("gsftopk(k) (xdvik REPLACE-WITH-VERSION) %s\n",
		      version);
		    puts(kpathsea_version_string);
		    puts("Copyright (C) 1993-1997 Paul Vojta.\n\
There is NO warranty.  You may redistribute this software\n\
under the terms of the GNU General Public License\n\
and the standard X consortium copyright notice.\n\
For more information about these matters, see the files\n\
named COPYING and xdvi.c.\n\
Primary author of gsftopk: Paul Vojta; -k maintainer: K. Berry.");
		}
#endif
		return 0;
	    }
	}

	if (mapfile == NULL)
	    mapfile = "psfonts.map";
	else if (mapline != NULL)
	    opt_oops("cannot specify both `--mapline' and `--mapfile'");

	if (argp != argv + (argc - 2))
	    opt_oops("need exactly two arguments");

	fontname = *argp++;
	fontlen = strlen(fontname);

	dpi = atof(*argp);
	if (dpi <= 0.0)
	    opt_oops("DPI argument `%s' must be a positive number", *argp);

#ifdef KPATHSEA
	kpse_set_progname(argv[0]);
	kpse_init_prog("GSFTOPK", (int) (dpi + 0.5), NULL, "cmr10");
	xputenv_int("KPATHSEA_DPI", (int) (dpi + 0.5));
#endif

#ifdef _AMIGA
	/* [CL] 21-Jun-97
	   This is quite silly but it really helps things when determining the
	   font supplier and family.
	*/
	putenv("GSFTOPKTFM=");
#endif

	if (!quiet) {
	    printf("%sversion %s", progname, version);
	    fflush(stdout);
	    col = 1;	/* any nonzero value will do */
	}

	if (mapline != NULL) {
	    maplinelen = strlen(mapline) + 1;
	    if (memcmp(mapline, fontname, fontlen) != 0
	      || (mapline[fontlen] != '\0' && !isspace(mapline[fontlen])))
		oops("font name does not match --mapline argument");
	}
	else {
	    FILE *config_file;

#ifndef KPATHSEA
	    config_file = search(CONFIGPATH, "TEXCONFIG", mapfile);
#else
	    kpse_make_tex_discard_errors = quiet;
	    config_file = kpse_open_file(mapfile, kpse_dvips_config_format);
#endif
	    if (config_file == NULL) oops("Cannot find file %s.", mapfile);

	    maplinelen = 80;
	    mapline = xmalloc(maplinelen);
	    do {
		int	len	= 0;

		if (fgets(mapline, maplinelen, config_file) == NULL)
		    oops("Cannot find font %s in %s.", fontname, mapfile);
		for (;;) {
		    i = strlen(mapline + len);

		    len += i;
		    if (len > 0 && mapline[len - 1] == '\n') {
			mapline[--len] = '\0';
			break;
		    }
		    if (len < maplinelen - 1) break;
		    maplinelen += 80;
		    mapline = xrealloc(mapline, maplinelen);
		    fgets(mapline + len, maplinelen - len, config_file);
		}
	    }
	    while (memcmp(mapline, fontname, fontlen) != 0
	      || (mapline[fontlen] != '\0' && !isspace(mapline[fontlen])));
	    fclose(config_file);
	}

	/*
	 * Parse the line from the map file.
	 */
	for (p = mapline + fontlen; *p != '\0'; ++p) {
	    if (isspace(*p)) continue;
	    if (*p == '<') {
		char	*q;
		char	endc;
		char	c;
		FILE	*f;

		++p;
		/* There may be two '<'s in front of the filename. */
		if (*p == '<') ++p;
		/* ... and maybe a '[' */
		if (*p == '[') ++p;
		q = p;
		while (*p != '\0' && !isspace(*p)) ++p;
		endc = *p;
		*p = '\0';
#ifdef KPATHSEA
		searchpath = kpse_find_file(q, kpse_tex_ps_header_format,true);
		f = searchpath ? fopen(searchpath, FOPEN_R_MODE) : NULL;
#else
		f = search(HEADERPATH, "DVIPSHEADERS", q);
#endif
		if (f == NULL) oops("Cannot find font file %s", q);
		/* search() also sets searchpath */
		addtodls(" (");
		addtodls(searchpath);
		c = getc(f);
		addtodls(c == '\0' ? ") ttload"
		  : c == '\200' ? ") brun"
		  : ") run");
		fclose(f);
		if (endc == '\0') break;
		continue;
	    }
	    else if (*p == '"') {
		char	*q;

		if (specinf != NULL)
		    *specp++ = ' ';
		else
		    specinf = specp = xmalloc(maplinelen);
		++p;
		q = strchr(p, '"');
		if (q == NULL) q = p + strlen(p);
		memcpy(specp, p, q - p);
		specp += q - p;
		p = q;
		if (*p == '\0') break;
		else continue;
	    }
	    else {
		PSname = p;
		while (*p != '\0' && !isspace(*p)) ++p;
		if (*p == '\0') break;
	    }
	    *p = '\0';
	}

#ifdef OLD_DVIPS
	/* Parse lines like `Symbol-Slanted "/Symbol .167 SlantFont"'. */
	if (*(p = specinf) == '/') {
	    PSname = ++p;
	    while (*p && !isspace(*p)) ++p;
	    if (*p) *p++ = '\0';
	    specinf = p;
	}
#endif /* OLD_DVIPS */

	if (specinf != NULL) {
	    *specp = '\0';
	    specinfo = specinf;
	}

	/*
	 *	Start up Ghostscript.
	 */

#ifdef KPATHSEA
	searchpath = kpse_find_file("render.ps", kpse_tex_ps_header_format,
				    true);
	render_ps = searchpath ? fopen (searchpath, FOPEN_R_MODE) : NULL;
#else
	render_ps = search(HEADERPATH, "DVIPSHEADERS", "render.ps");
#endif
	if (render_ps == NULL)
	    oops("Cannot find PS driver file \"render.ps\".");
	fclose(render_ps);

	sprintf(dpistr, "%f", dpi);

#ifdef _AMIGA

	mktemp(tmpname);

	sprintf(fngs, "fifo:%s/rweK", tmpname);
	sprintf(fngsf, "fifo:%s/rweKm", tmpname);

	std_in[1] = open(fngsf, O_WRONLY|O_TRUNC|O_CREAT, 511);

	if (std_in[1] == -1) {
	    perror("pipe");
	    return 1;
	}

	in = Open(fngs, MODE_OLDFILE);
	out = Open(fngs, MODE_NEWFILE);

	if (in && out) {
	    int error;
	    char *cmd;
	    char formatstr[] = "%s -dNODISPLAY -dNOGC -q -- \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"";
	    char *psfontname = (PSname != NULL ? PSname : fontname);
	    unsigned int len;

	    len = sizeof formatstr + strlen(gspath) + strlen(searchpath)
	      + strlen(psfontname) + (dlstring != NULL ? strlen(dlstring) : 0)
	      + strlen(specinfo) + strlen(dpistr);

	    cmd = xmalloc(len);

	    sprintf(cmd, formatstr, gspath, searchpath, psfontname,
	      dlstring != NULL ? dlstring : "", specinfo, dpistr);

	    error = SystemTags(cmd, SYS_Input, in,
				   SYS_Output, out,
				   SYS_Asynch, TRUE,
				   /* NP_StackSize, 50000, */
				   TAG_DONE, TAG_DONE);
	    free(cmd);

	    if (error == -1) {
		Close(in);
		Close(out);
		PrintFault(error, "System(gs)");
		exit(1);
	    }
	}
	else {
	    perror("pipes");
	    if (in) Close(in);
	    if (out) Close(out);
	    exit(1);
	}

#elif defined(WIN32)

	SetConsoleCtrlHandler(handle_sigterm, TRUE);
	if (pipe(std_in, 0, _O_TEXT | _O_NOINHERIT) != 0
	    || pipe(std_out, 0, _O_BINARY | _O_NOINHERIT) != 0) {
	    perror("pipe");
	    return 1;
	}
	_flushall();

	/* Put this into configuration, and next into registry */
	/* putenv("GS_LIB=i:\\gstools\\gs4.03;i:\\gstools\\fonts"); */

	oldin = dup(fileno(stdin));
	oldout = dup(fileno(stdout));

	if (_dup2(std_in[0], fileno(stdin)) != 0)
		perror("dup2 std_in");
	if (_dup2(std_out[1], fileno(stdout)) != 0)
		perror("dup2 std_out");
	_flushall();

	close(std_in[0]);
	close(std_out[1]);
	/*	fprintf(stderr, "Args : %s|%s|%s|%s|%s\n",
		searchpath,  PSname != NULL ? PSname : fontname,
		quotearg(dlstring != NULL ? dlstring : ""),
		quotearg(specinfo),
		dpistr ); */
	if ((gs_relative_path = getenv("GS_PATH")) != NULL)
	    strcpy(gs_absolute_path, gs_relative_path);
	else if (SearchPath(NULL, "gswin32c.exe",
		       ".exe", PATH_MAX, gs_absolute_path,
		       &gs_relative_path) == 0)
	    oops("Can't locate gswin32c.exe!\n");

	/* Fix Me : doesn't work when GS is not absolute. */
	if ((gs_pid = spawnlp(_P_NOWAIT,
			   gs_absolute_path,
			   "gswin32c.exe", "-dNOGC",
			   "-dNODISPLAY", "-q", "--", searchpath,
			   PSname != NULL ? PSname : fontname,
			   quotearg(dlstring != NULL ? dlstring : ""),
			   quotearg(specinfo),
			   dpistr, NULL)) == -1) {
	    perror("Spawn");
	    return 1;
	}
	Sleep(50); /* Don't know why, but emacs is doing it too ... */

	_dup2(oldin, fileno(stdin));
	_dup2(oldout, fileno(stdout));

	close(oldin);
	close(oldout);

#else /* neither _AMIGA nor WIN32 */

	if (pipe(std_in) != 0 || pipe(std_out) != 0) {
	    perror("pipe");
	    return 1;
	}

	/* Catch the signal for death of the child process. */

#ifdef HAVE_SIGACTION
	sigact.sa_handler = handle_sigchild;
	(void) sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = SA_NOCLDSTOP;
	(void) sigaction(SIGCHLD, &sigact, (struct sigaction *) NULL);
#else
	(void) signal(SIGCHLD, handle_sigchild);
#endif

	/*
	 * Also catch various termination signals, so that we can kill the
	 * gs process before terminating.
	 */

	(void) signal(SIGHUP, handle_sigterm);
	(void) signal(SIGINT, handle_sigterm);
	(void) signal(SIGQUIT, handle_sigterm);
	(void) signal(SIGTERM, handle_sigterm);

	fflush(stderr);		/* to avoid double flushing */
	gs_pid = vfork();
	if (gs_pid == 0) {
	    close(std_in[1]);
	    dup2(std_in[0], 0);
	    close(std_in[0]);
	    close(std_out[0]);
	    dup2(std_out[1], 1);
	    close(std_out[1]);
	    execlp(gspath, "gs", "-dNODISPLAY", "-dNOGC", "-q", "--",
		/* render.ps */ searchpath,
		PSname != NULL ? PSname : fontname,
		dlstring != NULL ? dlstring : "", specinfo, dpistr, NULL);
	    perror("gs");
	    exit(1);
	}
	if (gs_pid == -1) {
	    perror("fork");
	    exit(1);
	}

#endif /* neither _AMIGA nor WIN32 */

	/*
	 *	Open and read the tfm file.  If this takes a while, at least
	 *	it can overlap with the startup of Ghostscript.
	 */

	xfilename = xmalloc(fontlen + 10);
	strcpy(xfilename, fontname);
#ifdef KPATHSEA
#ifdef _AMIGA
	strcpy(tfm_path, kpse_find_file(xfilename, kpse_tfm_format, true));
	tfm_file = fopen(tfm_path, "r");
#else
	tfm_file = kpse_open_file(xfilename, kpse_tfm_format);
#endif /* _AMIGA */
#else /* not KPATHSEA */
	strcpy(xfilename + fontlen, ".tfm");
	tfm_file = search(TEXFONTS_DEFAULT, "TEXFONTS", xfilename);
#endif /* not KPATHSEA */
	if (tfm_file == NULL) oops("Cannot find tfm file.");
	for (i = 0; i < 12; ++i) {
	    int j;

	    j = (int) ((byte) getc(tfm_file)) << 8;
	    tfm_lengths[i] = j | (int) ((byte) getc(tfm_file));
	}
	checksum = getlong(tfm_file);
	design = getlong(tfm_file);
	fseek(tfm_file, 4 * (lh + 6), 0);
	p = charlist;
	for (cc = bc; cc <= ec; ++cc) {
	    width_index[cc] = (byte) getc(tfm_file);
	    if (width_index[cc] != 0) {
		sprintf(p, "%d ", cc);
		p += strlen(p);
	    }
	    (void) getc(tfm_file);
	    (void) getc(tfm_file);
	    (void) getc(tfm_file);
	}
	for (i = 0; i < nw; ++i) tfm_widths[i] = getlong(tfm_file);
	fclose(tfm_file);
	p[-1] = '\n';

	/* write the design size and character list to the file */
	sprintf(designstr, "%f\n", (float) design / (1 << 20));
	write(std_in[1], designstr, strlen(designstr));
	write(std_in[1], charlist, p - charlist);
	close(std_in[1]);

/*
 *	Read the output from Ghostscript.
 */

#ifdef _AMIGA
	if ((data_file = fopen(fngsf, "r")) == NULL) {
	    perror("GS_out");
	    exit(1);
	}
#else
	data_fd = std_out[0];
#endif

#ifdef _AMIGA
	/* No equivalent for the amiga, apparently */
#elif defined(WIN32)
	/* FIXME : is there something equivalent under Win32 ? */
#else /* neither _AMIGA nor WIN32 */
	/* Set data_fd for non-blocking I/O */
	if (fcntl(data_fd, F_SETFL, fcntl(data_fd, F_GETFL, 0) | O_NONBLOCK)
	  == -1)
	    perror("fcntl");
#endif /* neither _AMIGA nor WIN32 */

/*
 *	Create pk file and write preamble.
 */

	if (dosnames)
	    strcpy(xfilename + fontlen, ".pk");
	else
	    sprintf(xfilename + fontlen, ".%dpk", (int) (dpi + 0.5));

	if ((pk_file = fopen(xfilename, FOPEN_WBIN_MODE)) == NULL) {
	    perror(xfilename);
	    exit_toto_too();
	}
	putc(PK_PRE, pk_file);
	putc(PK_ID, pk_file);
	expect("V");	/* get GS version */
	i = strlen(line) - 2;
	if (i < 0 || i > 10) i = 0;
	line[1] = '/';
	if (!quiet) {
	    if (i > 0) fwrite(line + 1, 1, i, stdout);
	    putchar('\n');
	    col = 0;
	}
	putc(sizeof(progname) + sizeof(version) + i - 2, pk_file);
	fwrite(progname, 1, sizeof(progname) - 1, pk_file);
	fwrite(version, 1, sizeof(version) - 1, pk_file);
	if (i >= 0) {
	    fwrite(line + 1, 1, i, pk_file);
	}
	putlong(design);
	putlong(checksum);
	ppp = dpi / 72.27 * 65536.0 + 0.5;
	putlong(ppp);	/* hppp */
	putlong(ppp);	/* vppp */

/*
 *	Write the actual characters.
 */

	for (cc = bc; cc <= ec; ++cc)
	    if (width_index[cc] != 0)
		putglyph(cc);
#ifndef _AMIGA
	close(data_fd);
#else
	fclose(data_file);
#endif

#ifdef _AMIGA
	/* do nothing */
#elif defined(WIN32)
	if (_cwait(&status, gs_pid, 0) == -1) {
	    perror("wait");
	    exit_toto_too();
	}
	if (status != 0)
	    if (status & 0377)
		oops("Call to gs stopped by signal %d", status & 0177);
	    else oops("Call to gs returned nonzero status %d", status >> 8);
#else /* neither _AMIGA nor WIN32 */
	if (!gs_is_done)
	    wait_for_gs();
#endif

/*
 *	Write out identifying specials:
 *		jobname=(font)
 *		mag=1
 *		mode=modeless
 *		pixels_per_inch=(dpi)
 */

	putspecl("jobname=", fontname);
	putspecl("mag=1", NULL);
	putspecl("mode=modeless", NULL);
	sprintf(dpistr, "%d", (int) dpi);
	putspecl("pixels_per_inch=", dpistr);

/*
 *	Postamble
 */

	putc(PK_POST, pk_file);
	while (ftell(pk_file) % 4 != 0) putc(PK_NOP, pk_file);
	fclose(pk_file);
	if (!quiet) putchar('\n');
	col = 0;

#ifdef _AMIGA
	/* [CL] 21-Jun-97
	   The same silly thing to indicate the path of the tfm file
	*/
	{
	    char tmpstr[80];

	    sprintf(tmpstr, "GSFTOPKTFM=%s", tfm_path);
	    putenv(tmpstr);
	}
#endif

	return 0;
}
