/*
 * Copyright 1993,1994,1995,2005 by Ross Paterson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote
 *     products derived from this software without specific prior written
 *     permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 *  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * Ross Paterson <ross@soi.city.ac.uk>
 * 17 October 1995
 *
 * The following people have supplied bug fixes:
 *
 *   Simon Chow     <khsc@synoptics.com>
 *   Fung Fung Lee  <lee@simd.stanford.edu>
 *   Man-Chi Pong   <mcpong@cs.ust.hk>
 *   Steven Simpson <simpson@math.psu.edu>
 *   Charles Wang   <charles.wang@infores.com>
 *   Werner Lemberg <wl@gnu.org>
 *
 * Ross no longer maintains this code.  Please send bug reports to
 * Werner Lemberg <wl@gnu.org>.
 *
 */

/*
 * Two C interfaces to HBF files.
 *
 * The multiple interfaces make this code rather messy; I intend
 * to clean it up as experience is gained on what is really needed.
 *
 * There are also two modes of operation:
 * - the default is to read each bitmap from its file as demanded
 * - if IN_MEMORY is defined, the whole bitmap file is held in memory.
 *   In this case, if running under Unix, the bitmap files may be gzipped
 *   (but the filename used in the HBF file should be the name of the
 *   file before it was gzipped).
 */
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "hbf.h"

#ifdef __MSDOS__
#define msdos
#endif

/*
 * if the linker complains about an unresolved identifier '_strdup',
 * uncomment the following definition.
 */
/* #define NO_STRDUP */

#ifdef	__STDC__
#	define	_(x)	x
#else
#	define	_(x)	()
#endif

#define	reg	register

typedef	int	bool;
#define	TRUE	1
#define	FALSE	0

#define	Bit(n)	(1<<(7 - (n)))

/*
 * Messy file system issues
 */

#ifdef unix
#define	PATH_DELIMITER ':'
#define	RelativeFileName(fn)	((fn)[0] != '/')
#define	LocalFileName(fn)	(strchr(fn, '/') == NULL)
#endif /* unix */
#ifdef msdos
#define	PATH_DELIMITER ';'
#define	HasDrive(fn)	(isalpha((fn)[0]) && (fn)[1] == ':')
#ifdef __EMX__
#define RelativeFileName(fn)	(! HasDrive(fn) && \
				!((fn)[0] == '\\' || (fn)[0] == '/'))
#define LocalFileName(fn)	(! HasDrive(fn) && \
				strchr(fn, '\\') == NULL && \
				strchr(fn, '/') == NULL)
#else
#define	RelativeFileName(fn)	(! HasDrive(fn) && (fn)[0] != '\\')
#define	LocalFileName(fn)	(! HasDrive(fn) && strchr(fn, '\\') == NULL)
#endif /* __EMX__ */
#define	READ_BINARY	"rb"
#endif /* msdos */
#ifdef vms
#define	PATH_DELIMITER ','
#define	RelativeFileName(fn)	(strchr(fn, ':') == NULL && ((fn)[0] != '[' || (fn)[1] == '.' || (fn)[1] == '-'))
#define	LocalFileName(fn)	(strchr(fn, ':') == NULL && strchr(fn, ']') == NULL)
#endif

#ifndef	RelativeFileName
#define	RelativeFileName(fn)	FALSE
#endif

#ifndef	LocalFileName
#define	LocalFileName(fn)	FALSE
#endif

#ifndef READ_BINARY
#define	READ_BINARY	"r"
#endif

#define	MAX_FILENAME	1024

/*
 *	Internal structures
 */

typedef	unsigned char	byte;

#define PROPERTY	struct _PROPERTY
#define BM_FILE		struct _BM_FILE
#define B2_RANGE	struct _B2_RANGE
#define CODE_RANGE	struct _CODE_RANGE

PROPERTY {
	char		*prop_name;
	char		*prop_value;
	PROPERTY	*prop_next;
};

BM_FILE {
	char	*bmf_name;
#ifdef IN_MEMORY
	byte	*bmf_contents;
#else
	FILE	*bmf_file;
#endif
	long	bmf_size;
	BM_FILE	*bmf_next;
};

B2_RANGE {
	byte		b2r_start;
	byte		b2r_finish;
	B2_RANGE	*b2r_next;
};

typedef	unsigned short	CHAR;
typedef	unsigned int	CHAR_INDEX;	/* character index in file */
#define	BAD_CHAR_INDEX	0xffff

CODE_RANGE {
	CHAR		code_start;
	CHAR		code_finish;
	BM_FILE		*code_bm_file;
	long		code_offset;
	CHAR_INDEX	code_pos;
	bool		code_transposed;
	bool		code_inverted;
	CODE_RANGE	*code_next;
};

/*
 *	Extended internal version of HBF
 */

typedef struct {
	/* fields corresponding to the definition */
	HBF		public;
	/* plus internal stuff */
	char		*filename;
	byte		*bitmap_buffer;
	unsigned int	b2_size;	/* number of legal byte-2's */
	PROPERTY	*property;
	B2_RANGE	*byte_2_range;
	CODE_RANGE	*code_range;
	BM_FILE		*bm_file;
} HBF_STRUCT;

#define	FirstByte(code)		((code)>>8)
#define	SecondByte(code)	((code)&0xff)
#define	MakeCode(byte1,byte2)	(((byte1)<<8)|(byte2))

/* size of a bitmap in the file (may be affected by transposition) */
#define	FileBitmapSize(hbfFile,cp) \
		((cp)->code_transposed ? \
			(hbfBitmapBBox(hbfFile)->hbf_height + 7)/8 * \
				hbfBitmapBBox(hbfFile)->hbf_width : \
			HBF_BitmapSize(hbfFile))

#define	NEW(type)	((type *)malloc((unsigned)(sizeof(type))))

#define	QUOTE '"'

#define MAXLINE	1024

#ifdef WIN32
#define	strdup(x)	_strdup(x)
#else
	extern	char	*strdup _((const char *s));
#endif

static	void	add_b2r _((B2_RANGE **last_b2r, int start, int finish));
static	bool	add_code_range _((HBF_STRUCT *hbf, const char *line));
static	void	add_property _((HBF_STRUCT *hbf, const char *lp));
static	CHAR_INDEX	b2_pos _((HBF_STRUCT *hbf, HBF_CHAR code));
static	int	b2_size _((B2_RANGE *b2r));
static	void	clear_bbox _((HBF_BBOX *bbox));
static	void	clear_record _((HBF_STRUCT *hbf));
static	char	*concat _((const char *dir, int dirlen, const char *stem));
static	char	*expand_filename _((const char *name, const char *filename));
static	const	byte *get_bitmap
		_((HBF_STRUCT *hbf, HBF_CHAR code, byte *buffer));
static	byte	*local_buffer _((HBF_STRUCT *hbf));
static	void	invert _((byte *buffer, unsigned length));
#ifdef IN_MEMORY
static	bool	read_bitmap_file _((BM_FILE *bmf, FILE *f));
static	bool	copy_transposed
		_((HBF *hbf, byte *bitmap, const byte *source));
#else
static	bool	get_transposed _((HBF *hbf, FILE *f, byte *bitmap));
#endif
static	bool	match _((const char *lp, const char *sp));
static	bool	parse_file _((FILE *f, HBF_STRUCT *hbf));
static	FILE	*path_open
		_((const char *path, const char *filename, char **fullp));
static	bool	real_open _((const char *filename, HBF_STRUCT *hbf));

/* Error reporting */

int	hbfDebug;	/* set this for error reporting */

#ifdef	__STDC__
#include <stdarg.h>

static void
eprintf(const char *fmt, ...)
{
	if (hbfDebug) {
		va_list	args;

		(void)fprintf(stderr, "HBF: ");
		va_start(args, fmt);
		(void)vfprintf(stderr, fmt, args);
		va_end(args);
		(void)fprintf(stderr, "\n");
	}
}
#else /* ! __STDC__ */
/* poor man's variable-length argument list */
static void
eprintf(fmt, x1, x2, x3, x4, x5, x6, x7, x8, x9)
	const	char	*fmt;
	int	x1, x2, x3, x4, x5, x6, x7, x8, x9;
{
	if (hbfDebug) {
		(void)fprintf(stderr, "HBF: ");
		(void)fprintf(stderr, fmt, x1, x2, x3, x4, x5, x6, x7, x8, x9);
		(void)fprintf(stderr, "\n");
	}
}
#endif /* __STDC__ */

static void
clear_bbox(HBF_BBOX *bbox)
{
	bbox->hbf_width = bbox->hbf_height = 0;
	bbox->hbf_xDisplacement = bbox->hbf_yDisplacement = 0;
}

static void
clear_record(HBF_STRUCT *hbf)
{
	clear_bbox(&(hbf->public.hbf_bitmap_bbox));
	clear_bbox(&(hbf->public.hbf_font_bbox));
	hbf->property = NULL;
	hbf->filename = NULL;
	hbf->bitmap_buffer = NULL;
	hbf->byte_2_range = NULL;
	hbf->code_range = NULL;
	hbf->bm_file = NULL;
}

/*
 *	Byte-2 ranges
 */

static void
add_b2r(reg B2_RANGE **last_b2r, int start, int finish)
{
reg	B2_RANGE *b2r;

	b2r = NEW(B2_RANGE);
	while (*last_b2r != NULL && (*last_b2r)->b2r_start < start)
		last_b2r = &((*last_b2r)->b2r_next);
	b2r->b2r_next = *last_b2r;
	b2r->b2r_start = start;
	b2r->b2r_finish = finish;
	*last_b2r = b2r;
}

static CHAR_INDEX
b2_pos(HBF_STRUCT *hbf, HBF_CHAR code)
{
reg	B2_RANGE *b2r;
reg	unsigned c;
reg	CHAR_INDEX	pos;

	c = SecondByte(code);
	pos = 0;
	for (b2r = hbf->byte_2_range; b2r != NULL; b2r = b2r->b2r_next)
		if (b2r->b2r_start <= c && c <= b2r->b2r_finish)
			return pos + c - b2r->b2r_start;
		else
			pos += b2r->b2r_finish - b2r->b2r_start + 1;
	return BAD_CHAR_INDEX;
}

static int
b2_size(reg B2_RANGE *b2r)
{
reg	int	size;

	size = 0;
	for ( ; b2r != NULL; b2r = b2r->b2r_next)
		size += b2r->b2r_finish - b2r->b2r_start + 1;
	return size;
}

/* map a position to a character code */
static long
code_of(HBF_STRUCT *hbf, long pos)
{
	long	code;
	int	residue;
reg	B2_RANGE *b2r;

	code = pos / hbf->b2_size * 256;
	residue = pos % hbf->b2_size;
	for (b2r = hbf->byte_2_range; b2r != NULL; b2r = b2r->b2r_next)
		if (b2r->b2r_start + residue <= b2r->b2r_finish)
			return code + b2r->b2r_start + residue;
		else
			residue -= b2r->b2r_finish - b2r->b2r_start + 1;
	/* should never get here */
	return 0L;
}

/*
 *	String stuff
 */

static bool
match(reg const char *lp, reg const char *sp)
{
	while (*lp == *sp && *sp != '\0') {
		lp++;
		sp++;
	}
	return (*lp == '\0' || isspace((unsigned char)*lp)) && *sp == '\0';
}

#ifdef NO_STRDUP
char *
strdup(const char *s)
{
	char	*new_s;

	new_s = malloc((unsigned)strlen(s) + 1);
	strcpy(new_s, s);
	return new_s;
}
#endif

/*
 *	Properties
 */

static void
add_property(reg HBF_STRUCT *hbf, const char *lp)
{
reg	PROPERTY	*prop;
	char	tmp[MAXLINE];
reg	char	*tp;

	prop = NEW(PROPERTY);

	tp = tmp;
	while (*lp != '\0' && ! isspace((unsigned char)*lp))
		*tp++ = *lp++;
	*tp = '\0';
	prop->prop_name = strdup(tmp);

	while (*lp != '\0' && isspace((unsigned char)*lp))
		lp++;

	tp = tmp;
	if (*lp == QUOTE) {
		lp++;
		while (*lp != '\0' && ! (*lp == QUOTE && *++lp != QUOTE))
			*tp++ = *lp++;
	}
	else
		for (;;) {
			while (*lp != '\0' && ! isspace((unsigned char)*lp))
				*tp++ = *lp++;
			while (*lp != '\0' && isspace((unsigned char)*lp))
				lp++;
			if (*lp == '\0')
				break;
			*tp++ = ' ';
		}
	*tp = '\0';
	prop->prop_value = strdup(tmp);

	prop->prop_next = hbf->property;
	hbf->property = prop;
}

const char *
hbfProperty(HBF *hbfFile, const char *propName)
{
reg	HBF_STRUCT	*hbf;
reg	PROPERTY	*prop;

	hbf = (HBF_STRUCT *)hbfFile;
	for (prop = hbf->property; prop != NULL; prop = prop->prop_next)
		if (strcmp(prop->prop_name, propName) == 0)
			return prop->prop_value;
	return NULL;
}

/*
 *	Compatability routines
 */

const char *
HBF_GetProperty(HBF *handle, const char *propertyName)
{
	return hbfProperty(handle, propertyName);
}

int
HBF_GetFontBoundingBox(HBF_Handle handle,
		       unsigned int *width, unsigned int *height,
		       int *xDisplacement, int *yDisplacement)
{
	if (width != NULL)
		*width = hbfFontBBox(handle)->hbf_width;
	if (height != NULL)
		*height = hbfFontBBox(handle)->hbf_height;
	if (xDisplacement != NULL)
		*xDisplacement = hbfFontBBox(handle)->hbf_xDisplacement;
	if (yDisplacement != NULL)
		*yDisplacement = hbfFontBBox(handle)->hbf_yDisplacement;
	return 0;
}

int
HBF_GetBitmapBoundingBox(HBF_Handle handle,
			 unsigned int *width, unsigned int *height,
			 int *xDisplacement, int *yDisplacement)
{
	if (width != NULL)
		*width = hbfBitmapBBox(handle)->hbf_width;
	if (height != NULL)
		*height = hbfBitmapBBox(handle)->hbf_height;
	if (xDisplacement != NULL)
		*xDisplacement = hbfBitmapBBox(handle)->hbf_xDisplacement;
	if (yDisplacement != NULL)
		*yDisplacement = hbfBitmapBBox(handle)->hbf_yDisplacement;
	return 0;
}

/*
 * Prepend a directory to a relative filename.
 */
static char *
concat(const char *dir,		/* not necessarily null-terminated */
       int dirlen,		/* number of significant chars in dir */
       const char *stem)	/* relative filename */
{
	char	*fullname;

	if (dirlen == 0)	/* null: current directory */
		return strdup(stem);
#ifdef unix
	fullname = malloc(dirlen + strlen(stem) + 2);
	(void)sprintf(fullname, "%.*s/%s", dirlen, dir, stem);
#else
#ifdef msdos
	fullname = malloc(dirlen + strlen(stem) + 2);
	(void)sprintf(fullname, "%.*s\\%s", dirlen, dir, stem);
#else
#ifdef vms
	if (dir[dirlen-1] == ']' && stem[0] == '[' && stem[1] == '-') {
		dirlen--;
		stem++;
		fullname = malloc(dirlen + strlen(stem) + 2);
		(void)sprintf(fullname, "%.*s.%s", dirlen, dir, stem);
	}
	else {
		if (dir[dirlen-1] == ']' && stem[0] == '[' && stem[1] == '.') {
			dirlen--;
			stem++;
		}
		fullname = malloc(dirlen + strlen(stem) + 1);
		(void)sprintf(fullname, "%.*s%s", dirlen, dir, stem);
	}
#else
	fullname = strdup(stem);
#endif /* vms */
#endif /* msdos */
#endif /* unix */
	return fullname;
}

/*
 *	Bitmap files
 *
 *	If the host operating system has a heirarchical file system and
 *	the bitmap file name is relative, it is relative to the directory
 *	containing the HBF file.
 */
static char *
expand_filename(const char *name, const char *hbf_name)
{
#ifdef unix
reg	char	*s;
reg	int	size;

	size = name[0] != '/' && (s = strrchr(hbf_name, '/')) != NULL ?
		s - hbf_name + 1 : 0;
	s = malloc((unsigned)size + strlen(name) + 1);
	(void)sprintf(s, "%.*s%s", size, hbf_name, name);
	return s;
#else
#ifdef msdos
reg	char	*s;
reg	int	size;

#ifdef __EMX__
	s = (unsigned char *)hbf_name + strlen((unsigned char *)hbf_name) - 1;
	for(;;) {
		if (*s == '\\' || *s == '/')
			break;
		if (s == hbf_name) {
			s = NULL;
			break;
		}
		s--;
	}
	
	size = HasDrive(name) ? 0 :
		(name[0] == '\\' || name[0] == '/') ?
			(HasDrive(hbf_name) ? 2 : 0) :
		s != NULL ? s - hbf_name + 1 : 0;
#else
	size = HasDrive(name) ? 0 :
		name[0] == '\\' ? (HasDrive(hbf_name) ? 2 : 0) :
		(s = strrchr(hbf_name, '\\')) != NULL ?
			s - hbf_name + 1 : 0;
#endif /* __EMX__ */
	s = malloc((unsigned)size + strlen(name) + 1);
	(void)sprintf(s, "%.*s%s", size, hbf_name, name);
	return s;
#else
#ifdef vms
reg	char	*s;
reg	const	char	*copyto;
reg	int	size;

	if ((s = strchr(hbf_name, ']')) != NULL && RelativeFileName(name))
		return concat(hbf_name, (s - hbf_name) + 1, name);

	copyto = hbf_name;
	if ((s = strstr(copyto, "::")) != NULL && strstr(name, "::") == NULL)
		copyto = s+2;
	if ((s = strchr(copyto, ':')) != NULL && strchr(name, ':') == NULL)
		copyto = s+1;
	size = copyto - hbf_name;
	s = malloc((unsigned)size + strlen(name) + 1);
	(void)sprintf(s, "%.*s%s", size, hbf_name, name);
	return s;
#else
	return strdup(name);
#endif /* vms */
#endif /* msdos */
#endif /* unix */
}

static BM_FILE *
find_file(HBF_STRUCT *hbf, const char *filename)
{
	BM_FILE	**fp;
reg	BM_FILE	*file;
	FILE	*f;
	char	*bmfname;
#ifdef IN_MEMORY
#ifdef unix
	bool	from_pipe;
#endif
#endif

	for (fp = &(hbf->bm_file); *fp != NULL; fp = &((*fp)->bmf_next)) {
		bmfname = strrchr((*fp)->bmf_name, '/');
		bmfname = (bmfname) ? bmfname + 1 : (*fp)->bmf_name;
		if (strcmp(bmfname, filename) == 0)
			return *fp;
	}

	file = NEW(BM_FILE);
	if (file == NULL) {
		eprintf("out of memory");
		return NULL;
	}
	file->bmf_name = expand_filename(filename, hbf->filename);
	if (file->bmf_name == NULL) {
		free((char *)file);
		return NULL;
	}
	f = fopen(file->bmf_name, READ_BINARY);
#ifdef IN_MEMORY
#ifdef unix
	from_pipe = FALSE;
	if (f == NULL) {
		char	tmp[400];

		sprintf(tmp, "%s.gz", file->bmf_name);
		if ((f = fopen(tmp, "r")) != NULL) {
			fclose(f);
			sprintf(tmp, "gzcat %s.gz", file->bmf_name);
			if ((f = popen(tmp, "r")) != NULL)
				from_pipe = TRUE;
		}
	}
#endif /* unix */
#endif /* IN_MEMORY */
	if (f == NULL) {
		eprintf("can't open bitmap file '%s'", file->bmf_name);
		free(file->bmf_name);
		free((char *)file);
		return NULL;
	}
#ifdef IN_MEMORY
	if (! read_bitmap_file(file, f)) {
		free(file->bmf_name);
		free((char *)file);
		return NULL;
	}
#ifdef unix
	if (from_pipe)
		pclose(f);
	else
		fclose(f);
#else /* ! unix */
	fclose(f);
#endif /* ! unix */
#else /* ! IN_MEMORY */
	file->bmf_file = f;
	fseek(f, 0L, 2);
	file->bmf_size = ftell(f);
#endif /* ! IN_MEMORY */
	file->bmf_next = NULL;
	*fp = file;
	return file;
}

#ifdef IN_MEMORY
#define	GRAIN_SIZE	512

static bool
read_bitmap_file(BM_FILE *bmf, FILE *f)
{
	byte	*contents, *cp;
	long	size;
	int	c;

	size = 0;
	cp = contents = (byte *)malloc((unsigned)GRAIN_SIZE);
	if (contents == NULL) {
		eprintf("not enough space for bitmap file");
		return NULL;
	}
	while ((c = getc(f)) != EOF) {
		if (size%GRAIN_SIZE == 0) {
			contents = (byte *)realloc((char *)contents,
					(unsigned)(size + GRAIN_SIZE));
			if (contents == NULL) {
				eprintf("not enough space for bitmap file");
				return NULL;
			}
			cp = contents + size;
		}
		*cp++ = c;
		size++;
	}
	bmf->bmf_size = size;
	bmf->bmf_contents = (byte *)realloc((char *)contents, (unsigned)size);
	return TRUE;
}
#endif /* IN_MEMORY */

/*
 *	Code ranges
 */

/* check that a code range fits within its bitmap file */
static bool
too_short(HBF_STRUCT *hbf, CODE_RANGE *cp)
{
	int	bm_size;
	long	offset, end_offset;
	BM_FILE	*bmf;
	long	start, finish;

	bm_size = FileBitmapSize(&(hbf->public), cp);
	offset = cp->code_offset;
	start = cp->code_start;
	finish = cp->code_finish;
	end_offset = offset + bm_size *
			(hbf->b2_size*(long)FirstByte(finish) +
				b2_pos(hbf, finish) - cp->code_pos + 1);
	bmf = cp->code_bm_file;
	if (end_offset <= bmf->bmf_size)
		return FALSE;
	/* bitmap file is too short: produce a specific error message */
	if (offset > bmf->bmf_size)
		eprintf("bitmap file '%s' is shorter than offset 0x%04lx",
			bmf->bmf_name, offset);
	else if (offset + bm_size > bmf->bmf_size)
		eprintf("bitmap file '%s' too short: no room for any bitmaps at offset 0x%04lx",
			bmf->bmf_name, offset);
	else
		eprintf("bitmap file '%s' is too short - code range appears to be 0x%04lx-0x%04lx",
			bmf->bmf_name,
			start,
			code_of(hbf, cp->code_pos +
					(bmf->bmf_size - offset)/bm_size) - 1);
	return TRUE;
}

static const char *
skip_word(int n, const char *s)
{
	for ( ; n > 0; n--) {
		while (*s != '\0' && ! isspace((unsigned char)*s))
			s++;
		while (*s != '\0' && isspace((unsigned char)*s))
			s++;
	}
	return s;
}

/* optional keywords at the end of a CODE_RANGE line */
static void
parse_keywords(CODE_RANGE *cp, const char *s)
{
	for (s = skip_word(4, s) ; *s != '\0'; s = skip_word(1, s)) {
		switch (*s) {
		case 's': case 'S': case 't': case 'T':
			/* keyword "sideways" or "transposed" */
			cp->code_transposed = TRUE;
			break;
		case 'i': case 'I':
			/* keyword "inverted" */
			cp->code_inverted = TRUE;
		}
	}
}

static bool
add_code_range(HBF_STRUCT *hbf, const char *line)
{
	CODE_RANGE *cp;
	CODE_RANGE **cpp;
	long	start, finish;
	long	offset;
	char	filename[MAXLINE];
	BM_FILE	*bmf;
	CHAR_INDEX b2pos;

	if (sscanf(line, "HBF_CODE_RANGE %li-%li %s %li",
			   &start, &finish, filename, &offset) != 4) {
		eprintf("syntax error in HBF_CODE_RANGE");
		return FALSE;
	}
	/* code ranges are checked in real_open() */
	if ((bmf = find_file(hbf, filename)) == NULL)
		return FALSE;
	if ((cp = NEW(CODE_RANGE)) == NULL) {
		eprintf("out of memory");
		return FALSE;
	}

	cp->code_start = (CHAR)start;
	cp->code_finish = (CHAR)finish;
	cp->code_bm_file = bmf;
	cp->code_offset = offset;
	cp->code_transposed = cp->code_inverted = FALSE;
	parse_keywords(cp, line);
	/* insert it in order */
	for (cpp = &hbf->code_range;
	     *cpp != NULL && (*cpp)->code_finish < start;
	     cpp = &((*cpp)->code_next))
		;
	if (*cpp != NULL && (*cpp)->code_start <= finish) {
		eprintf("code ranges overlap");
		return FALSE;
	}
	cp->code_next = *cpp;
	*cpp = cp;

	/* set code_pos, and check range */
	if (start > finish) {
		eprintf("illegal code range 0x%04lx-0x%04lx", start, finish);
		return FALSE;
	}
	if ((b2pos = b2_pos(hbf, start)) == BAD_CHAR_INDEX) {
		eprintf("illegal start code 0x%04lx", start);
		return FALSE;
	}
	cp->code_pos = hbf->b2_size*(long)FirstByte(start) + b2pos;
	if ((b2pos = b2_pos(hbf, finish)) == BAD_CHAR_INDEX) {
		eprintf("illegal finish code 0x%04lx", finish);
		return FALSE;
	}
	/* check that the bitmap file has enough bitmaps */
	return ! too_short(hbf, cp);
}

/*
 *	Reading and parsing of an HBF file
 */

/* get line, truncating to len, and trimming trailing spaces */
static bool
get_line(char *buf, int len, FILE *f)
{
	int	c;
	char	*bp;

	bp = buf;
	for (;;) {
		if ((c = getc(f)) == EOF) {
			eprintf("unexpected end of file");
			return FALSE;
		}
		if (c == '\n' || c == '\r') {
			/* trim trailing space */
			while (bp > buf && isspace((unsigned char)*(bp-1)))
				bp--;
			*bp = '\0';
			return TRUE;
		}
		if (len > 0) {
			*bp++ = c;
			len--;
		}
	}
}

/* get next non-COMMENT line */
static bool
get_text_line(char *buf, int len, FILE *f)
{
	while (get_line(buf, len, f))
		if (*buf != '\0' && ! match(buf, "COMMENT"))
			return TRUE;
	return FALSE;
}

static bool
get_property(const char *line, const char *keyword, HBF_STRUCT *hbf)
{
	if (! match(line, keyword)) {
		eprintf("%s expected", keyword);
		return FALSE;
	}
	add_property(hbf, line);
	return TRUE;
}

static bool
get_bbox(const char *line, const char *keyword, HBF_BBOX *bbox)
{
	int	w, h, xd, yd;

	if (! match(line, keyword) ||
	    sscanf(line + strlen(keyword), "%i %i %i %i",
			&w, &h, &xd, &yd) != 4) {
		eprintf("%s expected", keyword);
		return FALSE;
	}
	if (w <= 0 || h <= 0) {
		eprintf("illegal %s dimensions %dx%d", keyword, w, h);
		return FALSE;
	}
	bbox->hbf_width = w;
	bbox->hbf_height = h;
	bbox->hbf_xDisplacement = xd;
	bbox->hbf_yDisplacement = yd;
	return TRUE;
}

/*
 *  HBFHeaderFile ::=
 * 	'HBF_START_FONT'		version			EOLN
 * 	'HBF_CODE_SCHEME'		word ...		EOLN
 * 	'FONT'				fontName		EOLN
 * 	'SIZE'				ptsize xres yres	EOLN
 * 	'HBF_BITMAP_BOUNDING_BOX'	w h xd yd		EOLN
 * 	'FONTBOUNDINGBOX'		w h xd yd		EOLN
 * 	X11R5FontPropertySection
 * 	'CHARS'				n			EOLN
 * 	HBFByte2RangeSection
 * 	HBFCodeRangeSection
 * 	'HBF_END_FONT'			EOLN .
 *
 * This implementation allows extra lines before HBF_END_FONT.
 * Anything after HBF_END_FONT is ignored.
 */

static bool
parse_file(FILE *f, reg HBF_STRUCT *hbf)
{
	char	line[MAXLINE];
	int	start, finish;

	if (! get_text_line(line, MAXLINE, f) ||
	    ! get_property(line, "HBF_START_FONT", hbf))
		return FALSE;

	if (! get_text_line(line, MAXLINE, f) ||
	    ! get_property(line, "HBF_CODE_SCHEME", hbf))
		return FALSE;

	if (! get_text_line(line, MAXLINE, f) ||
	    ! get_property(line, "FONT", hbf))
		return FALSE;

	if (! get_text_line(line, MAXLINE, f) ||
	    ! get_property(line, "SIZE", hbf))
		return FALSE;

	if (! get_text_line(line, MAXLINE, f) ||
	    ! get_bbox(line, "HBF_BITMAP_BOUNDING_BOX",
			&(hbf->public.hbf_bitmap_bbox)))
		return FALSE;

	if (! get_text_line(line, MAXLINE, f) ||
	    ! get_bbox(line, "FONTBOUNDINGBOX", &(hbf->public.hbf_font_bbox)))
		return FALSE;

	if (! get_text_line(line, MAXLINE, f))
		return FALSE;
	if (match(line, "STARTPROPERTIES")) {
		for (;;) {
			if (! get_text_line(line, MAXLINE, f))
				return FALSE;
			if (match(line, "ENDPROPERTIES"))
				break;
			add_property(hbf, line);
		}
		if (! get_text_line(line, MAXLINE, f))
			return FALSE;
	}

	if (match(line, "CHARS"))
		if (! get_text_line(line, MAXLINE, f))
			return FALSE;

	if (match(line, "HBF_START_BYTE_2_RANGES")) {
		for (;;) {
			if (! get_text_line(line, MAXLINE, f))
				return FALSE;
			if (match(line, "HBF_END_BYTE_2_RANGES"))
				break;
			if (sscanf(line, "HBF_BYTE_2_RANGE %i-%i",
					&start, &finish) != 2) {
				eprintf("HBF_BYTE_2_RANGE expected");
				return FALSE;
			}
			add_b2r(&(hbf->byte_2_range), start, finish);
		}
		if (! get_text_line(line, MAXLINE, f))
			return FALSE;
	}
	else
		add_b2r(&(hbf->byte_2_range), 0, 0xff);
	hbf->b2_size = b2_size(hbf->byte_2_range);

	if (! match(line, "HBF_START_CODE_RANGES")) {
		eprintf("HBF_START_CODE_RANGES expected");
		return FALSE;
	}
	for (;;) {
		if (! get_text_line(line, MAXLINE, f))
			return FALSE;
		if (match(line, "HBF_END_CODE_RANGES"))
			break;
		if (! add_code_range(hbf, line))
			return FALSE;
	}

	for (;;) {
		if (! get_text_line(line, MAXLINE, f))
			return FALSE;
		if (match(line, "HBF_END_FONT"))
			break;
		/* treat extra lines as properties (for private extensions) */
		add_property(hbf, line);
	}

	return TRUE;
}

static FILE *
path_open(const char *path, const char *filename, char **fullp)
{
	if (LocalFileName(filename) && path != NULL) {
#ifdef PATH_DELIMITER
		char	*fullname;
		FILE	*f;
		const	char	*p_next;

		for (;;) {
			p_next = strchr(path, PATH_DELIMITER);
			if (p_next == NULL)
				p_next = path + strlen(path);
			fullname = concat(path, p_next - path, filename);
			if ((f = fopen(fullname, "r")) != NULL) {
				*fullp = fullname;
				return f;
			}
			free(fullname);
			if (*p_next == '\0')
				break;
			path = p_next + 1;
		}
#endif
		return NULL;
	}
	else {
		*fullp = strdup(filename);
		return fopen(*fullp, "r");
	}
}

static bool
real_open(const char *filename, reg HBF_STRUCT *hbf)
{
	FILE	*f;

	f = path_open(getenv("HBFPATH"), filename, &(hbf->filename));
	if (f == NULL) {
		eprintf("can't read file '%s'", filename);
		return FALSE;
	}
	if (! parse_file(f, hbf)) {
		fclose(f);
		return FALSE;
	}
	fclose(f);
	return TRUE;
}

HBF *
hbfOpen(const char *filename)
{
reg	HBF_STRUCT *hbf;

	if ((hbf = NEW(HBF_STRUCT)) == NULL) {
		eprintf("can't allocate HBF structure");
		return NULL;
	}
	clear_record(hbf);
	if (real_open(filename, hbf))
		return &(hbf->public);
	hbfClose(&(hbf->public));
	return NULL;
}

int
HBF_OpenFont(const char *filename, HBF **ptrHandleStorage)
{
	return (*ptrHandleStorage = hbfOpen(filename)) == NULL ? -1 : 0;
}

/*
 *	Close files, free everything associated with the HBF.
 */

int
HBF_CloseFont(HBF *hbfFile)
{
reg	HBF_STRUCT	*hbf;
	PROPERTY	*prop_ptr, *prop_next;
	B2_RANGE	*b2r_ptr, *b2r_next;
	CODE_RANGE	*code_ptr, *code_next;
	BM_FILE		*bmf_ptr, *bmf_next;
	int		status;

	status = 0;
	hbf = (HBF_STRUCT *)hbfFile;

	if (hbf->filename != NULL)
		free(hbf->filename);
	if (hbf->bitmap_buffer != NULL)
		free(hbf->bitmap_buffer);

	for (prop_ptr = hbf->property;
	     prop_ptr != NULL;
	     prop_ptr = prop_next) {
		prop_next = prop_ptr->prop_next;
		free(prop_ptr->prop_name);
		free(prop_ptr->prop_value);
		free((char *)prop_ptr);
	}

	for (b2r_ptr = hbf->byte_2_range;
	     b2r_ptr != NULL;
	     b2r_ptr = b2r_next) {
		b2r_next = b2r_ptr->b2r_next;
		free((char *)b2r_ptr);
	}

	for (code_ptr = hbf->code_range;
	     code_ptr != NULL;
	     code_ptr = code_next) {
		code_next = code_ptr->code_next;
		free((char *)code_ptr);
	}

	for (bmf_ptr = hbf->bm_file;
	     bmf_ptr != NULL;
	     bmf_ptr = bmf_next) {
		bmf_next = bmf_ptr->bmf_next;
#ifdef IN_MEMORY
		free((char *)(bmf_ptr->bmf_contents));
#else
		if (bmf_ptr->bmf_file != NULL &&
		    fclose(bmf_ptr->bmf_file) < 0)
			status = -1;
#endif
		free(bmf_ptr->bmf_name);
		free((char *)bmf_ptr);
	}

	free((char *)hbf);

	return status;
}

void
hbfClose(HBF *hbfFile)
{
	(void)HBF_CloseFont(hbfFile);
}

/*
 *	Fetch a bitmap
 */

const byte *
hbfGetBitmap(HBF *hbf, HBF_CHAR code)
{
	return get_bitmap((HBF_STRUCT *)hbf, code, (byte *)NULL);
}

int
HBF_GetBitmap(HBF *hbf, HBF_CHAR code, byte *buffer)
{
	return get_bitmap((HBF_STRUCT *)hbf, code, buffer) == NULL ? -1 : 0;
}

/*
 * Internal function to fetch a bitmap.
 * If buffer is non-null, it must be used.
 */
static const byte *
get_bitmap(reg HBF_STRUCT *hbf, HBF_CHAR code, byte *buffer)
{
	CHAR_INDEX	pos, b2pos;
reg	CODE_RANGE	*cp;
	BM_FILE		*bmf;
	int		bm_size;
	long		offset;

	if ((b2pos = b2_pos(hbf, code)) == BAD_CHAR_INDEX)
		return NULL;
	pos = hbf->b2_size*FirstByte(code) + b2pos;
	for (cp = hbf->code_range; cp != NULL; cp = cp->code_next)
		if (cp->code_start <= code && code <= cp->code_finish) {
			bmf = cp->code_bm_file;
			bm_size = FileBitmapSize(&(hbf->public), cp);
			offset = cp->code_offset +
				   (long)(pos - cp->code_pos) * bm_size;
#ifdef IN_MEMORY
			if (buffer == NULL &&
			    ! cp->code_transposed && ! cp->code_inverted)
				return bmf->bmf_contents + offset;
#endif /* IN_MEMORY */
			if (buffer == NULL &&
			    ((buffer = local_buffer(hbf)) == NULL))
				return NULL;
#ifdef IN_MEMORY
			if (cp->code_transposed)
				copy_transposed(&(hbf->public),
						buffer,
						bmf->bmf_contents + offset);
			else
				memcpy((char *)buffer,
				       (char *)(bmf->bmf_contents + offset),
				       bm_size);
#else /* ! IN_MEMORY */
			if (fseek(bmf->bmf_file, offset, 0) != 0) {
				eprintf("seek error on code 0x%04x", code);
				return NULL;
			}
			if (cp->code_transposed ?
			    ! get_transposed(&(hbf->public), bmf->bmf_file,
						buffer) :
			    fread((char *)buffer,
					bm_size, 1, bmf->bmf_file) != 1) {
				eprintf("read error on code 0x%04x", code);
				return NULL;
			}
#endif /* IN_MEMORY */
			if (cp->code_inverted)
				invert(buffer, HBF_BitmapSize(&(hbf->public)));
			return buffer;
		}
	eprintf("code 0x%04x out of range", code);
	return NULL;
}

static byte *
local_buffer(HBF_STRUCT *hbf)
{
	if (hbf->bitmap_buffer == NULL &&
	    (hbf->bitmap_buffer = (byte *)malloc(HBF_BitmapSize(&(hbf->public)))) == NULL) {
		eprintf("out of memory");
		return NULL;
	}
	return hbf->bitmap_buffer;
}

static void
invert(byte *buffer, unsigned int length)
{
	for ( ; length > 0; length--)
		*buffer++ ^= 0xff;
}

#ifdef IN_MEMORY
static bool
copy_transposed(HBF *hbf, reg byte *bitmap, reg const byte *source)
{
reg	byte	*pos;
reg	byte	*bm_end;
	int	x;
	int	width;
reg	int	row_size;
reg	int	c;
reg	int	imask, omask;

	width = hbfBitmapBBox(hbf)->hbf_width;
	row_size = HBF_RowSize(hbf);
	bm_end = bitmap + HBF_BitmapSize(hbf);
	(void)memset((char *)bitmap, '\0', HBF_BitmapSize(hbf));
	for (x = 0; x < width; x++) {
		pos = bitmap + x/8;
		omask = Bit(x%8);
		/* y = 0 */
		for (;;) {
			c = *source++;
			for (imask = Bit(0); imask != 0; imask >>= 1) {
				/*
				 * At this point,
				 *
				 *	imask == Bit(y%8)
				 *	pos == bitmap + y*row_size + x/8
				 *
				 * We examine bit y of row x of the input,
				 * setting bit x of row y of the output if
				 * required, by applying omask to *pos.
				 */
				if ((c & imask) != 0)
					*pos |= omask;
				/* if (++y > height) goto end_column */
				pos += row_size;
				if (pos >= bm_end)
					goto end_column;
			}
		}
end_column:
		;
	}
	return TRUE;
}
#else /* ! IN_MEMORY */
static bool
get_transposed(HBF *hbf, FILE *f, reg byte *bitmap)
{
reg	byte	*pos;
reg	byte	*bm_end;
	int	x;
	int	width;
reg	int	row_size;
reg	int	c;
reg	int	imask, omask;

	width = hbfBitmapBBox(hbf)->hbf_width;
	row_size = HBF_RowSize(hbf);
	bm_end = bitmap + HBF_BitmapSize(hbf);
	(void)memset((char *)bitmap, '\0', HBF_BitmapSize(hbf));
	for (x = 0; x < width; x++) {
		pos = bitmap + x/8;
		omask = Bit(x%8);
		/* y = 0 */
		for (;;) {
			if ((c = getc(f)) == EOF)
				return FALSE;
			for (imask = Bit(0); imask != 0; imask >>= 1) {
				/*
				 * At this point,
				 *
				 *	imask == Bit(y%8)
				 *	pos == bitmap + y*row_size + x/8
				 *
				 * We examine bit y of row x of the input,
				 * setting bit x of row y of the output if
				 * required, by applying omask to *pos.
				 */
				if ((c & imask) != 0)
					*pos |= omask;
				/* if (++y > height) goto end_column */
				pos += row_size;
				if (pos >= bm_end)
					goto end_column;
			}
		}
end_column:
		;
	}
	return TRUE;
}
#endif /* ! IN_MEMORY */

/*
 * Call function on each valid code in ascending order.
 */
void
hbfForEach(reg HBF *hbfFile, void (*func)(HBF *, HBF_CHAR))
{
	HBF_STRUCT	*hbf;
	CODE_RANGE	*cp;
reg	B2_RANGE	*b2r;
reg	unsigned	byte1, byte2;
reg	unsigned	finish;

	hbf = (HBF_STRUCT *)hbfFile;
	for (cp = hbf->code_range; cp != NULL; cp = cp->code_next) {
		byte1 = FirstByte(cp->code_start);
		byte2 = SecondByte(cp->code_start);
		while (MakeCode(byte1, byte2) <= cp->code_finish) {
			for (b2r = hbf->byte_2_range;
			     b2r != NULL;
			     b2r = b2r->b2r_next) {
				if (byte2 < b2r->b2r_start)
					byte2 = b2r->b2r_start;
				finish = b2r->b2r_finish;
				if (byte1 == FirstByte(cp->code_finish) &&
				    finish > SecondByte(cp->code_finish))
					finish = SecondByte(cp->code_finish);
				while (byte2 <= finish) {
					(*func)(hbfFile,
						MakeCode(byte1, byte2));
					byte2++;
				}
			}
			byte1++;
			byte2 = 0;
		}
	}
}

const char *
hbfFileName(HBF *hbf)
{
	return ((HBF_STRUCT *)hbf)->filename;
}

long
hbfChars(HBF *hbfFile)
{
	HBF_STRUCT	*hbf;
	CODE_RANGE	*cp;
	long		num_chars;

	hbf = (HBF_STRUCT *)hbfFile;
	num_chars = 0;
	for (cp = hbf->code_range; cp != NULL; cp = cp->code_next)
		num_chars +=
			hbf->b2_size*FirstByte(cp->code_finish) +
			b2_pos(hbf, cp->code_finish) -
			(hbf->b2_size*FirstByte(cp->code_start) +
			b2_pos(hbf, cp->code_start)) + 1;
	return num_chars;
}

/*
 *	Functions also implemented as macros
 */

#ifdef hbfBitmapBBox
#undef hbfBitmapBBox
#endif

HBF_BBOX *
hbfBitmapBBox(HBF *hbf)
{
	return &(hbf->hbf_bitmap_bbox);
}

#ifdef hbfFontBBox
#undef hbfFontBBox
#endif

HBF_BBOX *
hbfFontBBox(HBF *hbf)
{
	return &(hbf->hbf_font_bbox);
}

const void *
hbfGetByte2Range(HBF *hbfFile, const void *b2r_pointer,
		 byte *startp, byte *finishp)
{
	HBF_STRUCT	*hbf;
	const B2_RANGE	*b2r;

	hbf = (HBF_STRUCT *)hbfFile;
	if (b2r_pointer == NULL)
		b2r = hbf->byte_2_range;
	else
		b2r = ((const B2_RANGE *)b2r_pointer)->b2r_next;
	if(b2r == NULL)
		return NULL;
	*startp = b2r->b2r_start;
	*finishp = b2r->b2r_finish;
	return (const void *)b2r;
}

const void *
hbfGetCodeRange(HBF *hbfFile, const void *code_pointer,
		HBF_CHAR *startp, HBF_CHAR *finishp)
{
	HBF_STRUCT	*hbf;
	const CODE_RANGE	*cp;

	hbf = (HBF_STRUCT *)hbfFile;
	if (code_pointer == NULL)
		cp = hbf->code_range;
	else
		cp = ((const CODE_RANGE *)code_pointer)->code_next;
	if(cp == NULL)
		return NULL;
	*startp = cp->code_start;
	*finishp = cp->code_finish;
	return (const void *)cp;
}
