/*
 * makepsres.c
 *
 * (c) Copyright 1991, 1994 Adobe Systems Incorporated.
 * All rights reserved.
 * 
 * Permission to use, copy, modify, distribute, and sublicense this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notices appear in all copies and that
 * both those copyright notices and this permission notice appear in
 * supporting documentation and that the name of Adobe Systems Incorporated
 * not be used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  No trademark license
 * to use the Adobe trademarks is hereby granted.  If the Adobe trademark
 * "Display PostScript"(tm) is used to describe this software, its
 * functionality or for any other purpose, such use shall be limited to a
 * statement that this software works in conjunction with the Display
 * PostScript system.  Proper trademark attribution to reflect Adobe's
 * ownership of the trademark shall be given whenever any such reference to
 * the Display PostScript system is made.
 * 
 * ADOBE MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THE SOFTWARE FOR
 * ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 * ADOBE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON- INFRINGEMENT OF THIRD PARTY RIGHTS.  IN NO EVENT SHALL ADOBE BE LIABLE
 * TO YOU OR ANY OTHER PARTY FOR ANY SPECIAL, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE, STRICT LIABILITY OR ANY OTHER ACTION ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  ADOBE WILL NOT
 * PROVIDE ANY TRAINING OR OTHER SUPPORT FOR THE SOFTWARE.
 * 
 * Adobe, PostScript, and Display PostScript are trademarks of Adobe Systems
 * Incorporated which may be registered in certain jurisdictions
 * 
 * Author:  Adobe Systems Incorporated
 */

#include <stdio.h>
#include <errno.h>
#include <ctype.h>

#ifdef XENVIRONMENT
#include <X11/Xos.h>
#else
#include <string.h>
#include <sys/types.h>
#endif

#include <sys/stat.h>

#include <dirent.h>

#if NeXT
# include <sys/dir.h>
#endif

#ifndef S_ISDIR
#ifdef S_IFDIR
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#else
#define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR)
#endif /* S_IFDIR */
#endif /* S_ISDIR */

#define	true	 1
#define	false	 0

/* We'd really like to use a shorter line length to make the files more
   readable, but some X servers have problems handling continuation lines.
   Make the line length very long to make it unlikely that the lines that
   the server is likely to care about (in particular, FontOutline) will end
   up getting wrapped. */
#define MAXLINELEN 250

#ifndef DEBUG
#define DEBUG false
#endif

#define	QUOTE 042

#define HASHSIZE 2048

/* Command line information */

char **directories;
int *directoryLen;
int directoryCount;
int recursive;
int discard;
int keep;
char *outputFilename;
char **inputFiles;
int inputCount;
int makeExclusive;
int interactive;
int strict;
int noPrefix;
int issueWarnings;
int noBackup;
int noSuffix;

typedef struct _t_Resource {
  char *name;
  char *file;
  int noPrefix;
  struct _t_Resource *next;
} Resource;

typedef struct _t_Duplicate {
  char *name;
  char *file1;
  char *file2;
  struct _t_Duplicate *next;
} Duplicate;

typedef struct _t_Category {
  char *name;
  Resource *list;
  Resource **hash;	/* Currently used only for mkpsresPrivate */
  Duplicate *duplicates;
  struct _t_Category *next;
} Category;

char *program;

extern char *malloc(), *realloc();

#if !defined(__NetBSD__)
# if !defined(__GLIBC__)
extern char *sys_errlist[];
# else
extern const char *const sys_errlist[];
# endif
#endif

extern int errno;

#define BUFFER_SIZE 1024
static char lineBuffer[BUFFER_SIZE];

Category *categories;

static char *ckmalloc(size, whynot)
    int size;
    char *whynot;
{
    char *result;

    if (size == 0) size = 1;
    result = malloc(size);
    if (result == NULL) {
	fprintf(stderr, "%s:  %s\n", program, whynot);
	exit(1);
    }
    return result;
}

static char *ckrealloc(ptr, size, whynot)
    char *ptr;
    int size;
    char *whynot;
{
    char *result;

    if (size == 0) size = 1;
    result = realloc(ptr, size);
    if (result == NULL) {
	fprintf(stderr, "%sf : %s\n", program, whynot);
	exit(1);
    }
    return result;
}

static char *ckcalloc(count, size, whynot)
    int count;
    int size;
    char *whynot;
{
    char *result;

    if (size == 0) size = 1;
    if (count == 0) count = 1;
    result = (char *) calloc(count, size);
    if (result == NULL) {
	fprintf(stderr, "%s:  %s\n", program, whynot);
	exit(1);
    }
    return result;
}


static Category *AddCategory (name)
  char *name;
{
  Category *newCategory = (Category *) ckcalloc (1, sizeof (Category),
		"Failed to allocate Category record.");

  newCategory->name = (char *) ckmalloc (strlen (name) + 1,
					 "Failed to allocate Category name.");

  strcpy (newCategory->name, name);

  if (categories == NULL) {
    categories = newCategory;
  } else {
    /* Insert into alphabetical position */
    Category *current,
           *previous;

    current = previous = categories;

    while (current != NULL) {
      if (strcmp (current->name, name) > 0) {
	break;
      } else {
	previous = current;
	current = current->next;
      }
    }

    if (current == NULL) {
      newCategory->next = NULL;
      previous->next = newCategory;
    } else {
      newCategory->next = current;

      if (current == categories) {
	categories = newCategory;
      } else {
	previous->next = newCategory;
      }
    }
  }

  return (newCategory);
}


static Category *FindCategory (name)
  char *name;
{
  Category *category = categories;

  while (category != NULL) {
    if (strcmp (category->name, name) == 0)
      break;
    else
	category = category->next;
  }

   if (category == NULL)
     category = AddCategory (name);

  return (category);
}


int Hash(string)
    char *string;
{
    int hash = 0;
    unsigned char *ch = (unsigned char *) string;

    while (1) {
	if (*ch == '\0') return hash % HASHSIZE;
	if (*(ch+1) == '\0') {
	    hash += *ch;
	    return hash % HASHSIZE;
	}
	hash += *ch++ + (*ch++ << 8);
    }
}

static void AddHashedResource(resource, category)
    Resource *resource;
    Category *category;
{
    Resource *current, *previous;
    int     comparison, hash;

    if (category->hash == NULL) {
	category->hash = (Resource **) ckcalloc(HASHSIZE, sizeof(Resource *),
					"Failed to allocate hash table.");
    }

    hash = Hash(resource->file);
    current = previous = category->hash[hash];

    while (current != NULL) {
	comparison = strcmp (current->file, resource->file);
	if (comparison > 0) break;

	if (comparison == 0 &&
	    strcmp(current->name, resource->name) != 0) break;

	previous = current;
	current = current->next;
    }

    if (category->hash[hash] == NULL) {
	category->hash[hash] = resource;
	resource->next = NULL;

    } else if (current == NULL) {
	resource->next = NULL;
	previous->next = resource;

    } else {
	resource->next = current;

	if (current == category->hash[hash]) {
	    category->hash[hash] = resource;
	} else {
	    previous->next = resource;
	}
    }
}

void EnterDuplicateWarning(category, res1, res2)
    Category *category;
    Resource *res1, *res2;
{
    Duplicate *dup, *previous, *current;

    if (!issueWarnings) return;

    dup = (Duplicate *) ckcalloc(1, sizeof(Duplicate),
				 "Failed to allocate Duplicate record.");

    dup->name = res1->name;
    dup->file1 = res1->file;
    dup->file2 = res2->file;

    current = previous = category->duplicates;

    while (current != NULL) {
	if (strcmp (current->name, res1->name) >= 0) break;
	previous = current;
	current = current->next;
    }

    if (category->duplicates == NULL) category->duplicates = dup;
    else if (current == NULL) {
	dup->next = NULL;
	previous->next = dup;
    } else {
	dup->next = current;

	if (current == category->duplicates) category->duplicates = dup;
	else previous->next = dup;
    }
}

static void AddResource (categoryName, resourceName, fileName, noPrefix)
  char *resourceName;
  char *categoryName;
  char *fileName;
  int noPrefix;
{
  Category *category = FindCategory (categoryName);
  Resource *resource,
         *current,
         *previous;
  int     comparison;

  resource = (Resource *) ckcalloc (1, sizeof (Resource),
				    "Failed to allocate Resource record.");

  resource->name = ckmalloc (strlen (resourceName) + 1,
			     "Failed to allocate Resource name.");

  strcpy (resource->name, resourceName);

  if (fileName[0] == '.' && fileName[1] == '/') fileName+=2;

  resource->file = ckmalloc (strlen (fileName) + 1,
			     "Failed to allocate Resource filename.");

  strcpy (resource->file, fileName);

  resource->noPrefix = noPrefix;

  if (strcmp(categoryName, "mkpsresPrivate") == 0) {
      AddHashedResource(resource, category);
      return;
  }

  current = previous = category->list;

  while (current != NULL) {
      comparison = strcmp (current->name, resourceName);
      if (comparison > 0) break;
      else if (comparison == 0) {
	  comparison = strcmp (current->file, fileName);
	  if (comparison > 0) {
	      if (strcmp(categoryName, "FontBDFSizes") != 0 &&
		  strcmp(categoryName, "FontFamily") != 0 &&
		  strcmp(categoryName, "mkpsresPrivate") != 0) {
		  EnterDuplicateWarning(category, current, resource);
	      }
	      break;
	  } else if (comparison == 0) {		/* Same file */
	      free (resource->name);
	      free (resource->file);
	      free (resource);
	      return;
	  }
      }
      previous = current;
      current = current->next;
  }

  if (category->list == NULL) {
    category->list = resource;
  } else if (current == NULL) {
    resource->next = NULL;
    previous->next = resource;
  } else {
    resource->next = current;

    if (current == category->list) {
      category->list = resource;
    } else {
      previous->next = resource;
    }
  }
}

int FindResource(categoryName, resourceName)
  char *categoryName;
  char *resourceName;
{
    Category *category = FindCategory (categoryName);
    Resource *resource;
    int i;

    for (resource = category->list;
	 resource != NULL && strcmp(resource->name, resourceName) != 0;
	 resource = resource->next) {}

    if (resource != NULL) return true;

    if (category->hash == NULL) return false;

    for (i = 0; i < HASHSIZE; i++) {
	for (resource = category->hash[i];
	     resource != NULL && strcmp(resource->name, resourceName) != 0;
	     resource = resource->next) {}
	if (resource != NULL) return true;
    }

    return false;
}


typedef struct _t_UPRResource {
  char *name;
  char *file;
  char *category;
  int found;
  int noPrefix;
  struct _t_UPRResource *next;
} UPRResource;

UPRResource *UPRresources[HASHSIZE];

#if DEBUG
int bucketCount[HASHSIZE];
int totalHashed = 0;
#endif

static void AddUPRResource (categoryName, resourceName, fileName, prefix,
			    noPrefix)
  char *resourceName;
  char *categoryName;
  char *fileName;
  char *prefix;
  int noPrefix;
{
  UPRResource *resource, *current, *previous;
  int     comparison, hash;

  if (noPrefix || prefix == NULL) {
      prefix = "";
      if (fileName[0] == '.' && fileName[1] == '/') fileName+=2;
  } else {
      prefix++; /* Skip over leading / */
      if (prefix[0] == '.' && prefix[1] == '/') prefix += 2;
  }

  resource = (UPRResource *) ckcalloc (1, sizeof (UPRResource),
				    "Failed to allocate Resource record.");

  resource->name = ckmalloc (strlen (resourceName) + 1,
			     "Failed to allocate Resource name.");

  strcpy (resource->name, resourceName);

  resource->file = ckmalloc (strlen (fileName) + strlen(prefix) + 2,
			     "Failed to allocate Resource filename.");

  if (prefix != NULL && prefix[0] != '\0') {
      strcpy (resource->file, prefix);
      strcat (resource->file, "/");
      strcat (resource->file, fileName);
  } else strcpy (resource->file, fileName);

  resource->category = ckmalloc (strlen (categoryName) + 1,
				 "Failed to allocate Resource name.");

  strcpy(resource->category, categoryName);

  resource->noPrefix = noPrefix;
  resource->found = false;

  hash = Hash(resource->file);
  current = previous = UPRresources[hash];

  while (current != NULL) {
      comparison = strcmp (current->file, resource->file);
      if (comparison > 0) break;

      if (comparison == 0) {
	  if (noPrefix) break;

	  if (strcmp(current->name, resource->name) != 0 ||
	      strcmp(current->category, resource->category) != 0) { /* Same */
	      if (strcmp(current->category, "mkpsresPrivate") == 0 &&
		  strcmp(current->name, "NONRESOURCE") == 0) {

		  /* Replace "NONRESOURCE" entry with resource one */
		  free(current->name);
		  current->name = resource->name;
		  free(current->category);
		  current->category = resource->category;
		  free(resource->file);
		  free (resource);
		  return;
	      }
	      fprintf(stderr,
		  "%s:  Warning:  file %s identified as different resources\n",
		  program, resource->file);
	      fprintf(stderr, "            Using %s\n", current->category);
	  }
	  free (resource->name);
	  free (resource->file);
	  free (resource->category);
	  free (resource);
	  return;
      }
      previous = current;
      current = current->next;
  }

  if (UPRresources[hash] == NULL) {
      UPRresources[hash] = resource;
      resource->next = NULL;

  } else if (current == NULL) {
      resource->next = NULL;
      previous->next = resource;
  } else {
      resource->next = current;

      if (current == UPRresources[hash]) {
	  UPRresources[hash] = resource;
      } else {
	  previous->next = resource;
      }
  }
#if DEBUG
  totalHashed++;
  bucketCount[hash]++;
#endif
}


void AddUPRResourceBDF(font, sizes)
    char *font;
    char *sizes;
{
    char *ch = sizes;
    char *buf;

    while (*ch != '\0') {
	while (*ch != '\0' && *ch != ',') ch++;
	if (*ch == ',') {
	    *ch = '\0';
	    if (*sizes != '\0') {
		/* Stick in the font size to spread out the hash table */

		buf = ckmalloc(strlen(font) + strlen(sizes) + 2,
			       "Failed to allocate BDF string");
		sprintf(buf, "%s,%s", font, sizes);
		AddUPRResource("FontBDFSizes", font, buf, NULL, true);
		free(buf);
	    }
	    sizes = ++ch;
	}
    }
}

void AddUPRResourceFontFamily(family, faces)
    char *family;
    char *faces;
{
    char *ch = faces, *chunk = faces;
    char old;

    while (true) {
	while (true) {
	    while (*ch != '\0' && *ch != ',') ch++;
	    if (*ch == '\0') return;
	    if (ch > faces && *(ch-1) == '\\') ch++;
	    else break;
	}
	/* Found the first , look for the second */
	ch++;
	while (true) {
	    while (*ch != '\0' && *ch != ',') ch++;
	    if (*ch == '\0') break;
	    if (*(ch-1) == '\\') ch++;
	    else break;
	}

	old = *ch;
	*ch = '\0';
	AddUPRResource("FontFamily", family, chunk, NULL, true);
	if (old == '\0') return;
	chunk = ++ch;
    }
}

static int SkipWhiteSpace(file)
    FILE *file;
{
    int c;

    while (1) {
	c = fgetc(file);
	if (c == ' ' || c == '\t') continue;
	if (c == EOF) return false;
	ungetc(c, file);
	return true;
    }
}


static int ReadItem(file, buf, size)
    FILE *file;
    char *buf;
    int size;
{
    int c;
    char closechar, openchar;
    int count = 0, nesting = 0;;

    openchar = '\0';

    c = fgetc(file);
    if (c == EOF) return false;
    if (c == '(') {closechar = ')'; openchar = c;}
    else if (c == '[') {closechar = ']'; openchar = c;}
    else if (c == QUOTE) closechar = QUOTE;
    else if (c == '/') closechar = '\0';
    else {
	closechar = '\0';
	ungetc(c, file);
    }

    while (count < size) {
	c = fgetc(file);
	if (openchar != '\0' && c == openchar) nesting++;
	if (c == EOF) break;
	if (c == closechar) {
	    if (nesting == 0) break;
	    else nesting--;
	}
	if (closechar == '\0' && strchr(" \t\n\r", c) != NULL) break;
	buf[count++] = c;
    }

    buf[count] = '\0';
    return true;
}

static char *FindKeyValue (file, key)
  FILE *file;
  char *key;
{
  char    lineKey[64];
  char   *result = NULL;

  while (true) {
    if (fgets (lineBuffer, BUFFER_SIZE, file) == NULL)
      break;

    sscanf (lineBuffer, "%64[%a-zA-Z]", lineKey);
    if (strcmp (key, lineKey) == 0) {
      result = strchr (lineBuffer, ' ');
      if (result != NULL) {
	result++;
	break;
      }
    }
  }

  return (result);
}

static void StripName (name)
  char *name;
{
  char    closeCharacter = '\0';
  char   *pointer;

  if (name[0] == '/') strcpy(name, name+1);

  while (true) {
    if (name[0] == '(') closeCharacter = ')';
    else if (name[0] == QUOTE) closeCharacter = QUOTE;
    else break;

    pointer = strrchr (name, closeCharacter);

    if (pointer != NULL) {
      *pointer = '\0';
      strcpy (name, name + 1);
    } else break;			/* No close character */
  }

  pointer = strrchr (name, '\r');

  if (pointer != NULL) *pointer = '\0';
  else {
    pointer = strrchr (name, '\n');
    if (pointer != NULL) *pointer = '\0';
  }
}


static char *bugFamilies[] = {
    "Berkeley", "CaslonFiveForty", "CaslonThree", "GaramondThree",
    "Music", "TimesTen", NULL
	};

static char *fixedFamilies[] = {
    "ITC Berkeley Oldstyle", "Caslon 540", "Caslon 3", "Garamond 3",
    "Sonata", "Times 10", NULL
};

static char *missingFoundries[] = {
    "Berthold ", "ITC ", "Linotype ", NULL
};

static int missingFoundryLen[] = {
    9, 4, 9, 0
};

static void MungeFontNames(name, family, fullname, weight,
			   familyReturn, fullnameReturn, faceReturn)
    char *name, *family, *fullname, *weight;
    char *familyReturn, *fullnameReturn, *faceReturn;
{
    register char *src, *dst, prev;
    char buf[256];
    int digits = 0;
    int i, diff;

    /* Copy the fullname into buf, enforcing one space between words.
       Eliminate leading digits and spaces, ignore asterisks, if the
       full name ends with 5 digits strip them, and replace periods that
       aren't followed by a space with a space.  If leading digits are
       followed by " pt " skip that too. */

    dst = buf;
    prev = ' ';
    src = fullname; 
    while (isdigit(*src)) src++;
    while (*src == ' ' || *src == '\t') src++;
    if (strncmp(src, "pt ", 3) == 0) src += 3;
    else if (strncmp(src, "pt. ", 4) == 0) src += 4;

    while (*src != '\0') {
	if (*src == '*') {
	    src++;
	    continue;
	}

	if (*src == '.') {
	    if (*(src+1) != ' ') {
		prev = *dst++ = ' ';
	    } else prev = *dst++ = '.';
	    src++;
	    continue;
	}

	if (isdigit(*src)) digits++;
	else digits = 0;

	if (isupper(*src)) {
	    if (prev != ' ' && (islower(*(src+1)) || islower(prev))) {
		*dst++ = ' ';
		prev = *dst++ = *src++;
	    } else prev = *dst++ = *src++;

	} else if (*src == ' ' || *src == '\t') {
	    if (prev == ' ') {
		src++;
		continue;
	    }
	    prev = *dst++ = ' ';
	    src++;

	} else prev = *dst++ = *src++;
    }

    if (digits == 5) {
	dst -= 5;
    }
    if (dst > buf && *(dst-1) == ' ') dst--;

    *dst = '\0';

    if (strcmp(name, "FetteFraktur-Dfr") == 0) strcat(buf, " Black Dfr");
    else if (strcmp(name, "Linotext-Dfr") == 0) strcat(buf, " Dfr");

    if (strncmp(fullname, "pt ", 3) == 0) {
	src = buf + 2;
	while (*++src != '\0') *(src-3) = *src;
	*(src-3) = '\0';
    }

    strcpy(fullnameReturn, buf);

    /* From here on fullname should not be used */

    /* Done with the full name; now onto the family */

    for (i = 0; bugFamilies[i] != NULL; i++) {
	diff = strcmp(family, bugFamilies[i]);
	if (diff < 0) break;
	if (diff == 0) {
	    strcpy(familyReturn, fixedFamilies[i]);
	    goto FAMILY_DONE;
	}
    }

    /* Copy the family into buf, enforcing one space between words */

    dst = buf;
    prev = ' ';
    src = family; 

    while (*src != '\0') {
	if (isupper(*src)) {
	    if (prev != ' ' && (islower(*(src+1)) || islower(prev))) {
		*dst++ = ' ';
		prev = *dst++ = *src++;
	    } else prev = *dst++ = *src++;

	} else if (*src == ' ' || *src == '\t') {
	    if (prev == ' ') {
		src++;
		continue;
	    }
	    prev = *dst++ = ' ';
	    src++;

	} else prev = *dst++ = *src++;
    }

    if (dst > buf && *(dst-1) == ' ') dst--;
    *dst = '\0';

    /* Compensate for fonts with foundries in the full name but not the
       family name by adding to the family name */
 
    for (i = 0; missingFoundries[i] != NULL; i++) {
	diff = strncmp(fullnameReturn, missingFoundries[i],
		       missingFoundryLen[i]);
	if (diff > 0) continue;
	if (diff == 0 && strncmp(buf, missingFoundries[i],
		       missingFoundryLen[i] != 0)) {
	    while (dst >= buf) {
		*(dst+missingFoundryLen[i]) = *dst;
		dst--;
	    }
	    strncpy(buf, missingFoundries[i], missingFoundryLen[i]);
	}
	break;
    }

    /* From here on dst no longer points to the end of the buffer */

    if (strncmp(fullnameReturn, "Helvetica Rounded ", 18) == 0) {
	strcat(buf, " Rounded");
    }

    strcpy(familyReturn, buf);

FAMILY_DONE:

    /* From here on family should not be used */

    /* Now to find the face in all this */

    src = fullnameReturn;
    dst = familyReturn;
    while (*dst == *src && *dst != '\0') {
        src++;
        dst++;
    }
    if (*src == ' ') src++;

    if (*src == '\0') {
	if (*weight != '\0') {
	    /* Handle Multiple Master fonts */
	    if (strcmp(weight, "All") == 0) src = "Roman";
	    else {
		if (islower(weight[0])) weight[0] = toupper(weight[0]);
		src = weight;
	    }
	} else src = "Medium";
    }

    strcpy(faceReturn, src);
}


void StripComments(buf)
    char *buf;
{
    register char *ch = buf;

    while (true) {
	while (*ch != '%' && *ch != '\0') ch++;
	if (*ch == '\0') break;
	if (ch == buf || *(ch-1) != '\\') {
	    *ch = '\0';
	    break;
	}
	ch++;
    }

    /* ch points to '\0' right now */

    if (ch == buf) return;
    ch--;

    while (ch > buf && (*ch == ' ' || *ch == '\t' || *ch == '\n')) {
	*ch = '\0';
	ch--;
    }

    if (ch == buf && (*ch == ' ' || *ch == '\t' || *ch == '\n')) *ch = '\0';
}

/* Caller must free returned line */

char *GetWholeLine(file)
    FILE *file;
{
    char *line;
    int len, oldlen;

    if (fgets (lineBuffer, BUFFER_SIZE, file) == NULL) return NULL;

    StripComments(lineBuffer);
    
    len = strlen(lineBuffer); 
    line = ckmalloc(len+1, "Failed to allocate input line.");
    strcpy(line, lineBuffer);

    if (line[len-1] == '\\') {	/* Continued... */
	line[len-1] = '\0';
	oldlen = len-1;
	while (true) {
	    if (fgets(lineBuffer, BUFFER_SIZE, file) == NULL) {
		return line;
	    }

	    StripComments(lineBuffer);
	    if (lineBuffer[0] == '\0') return line;

	    len = strlen(lineBuffer);
	    line = ckrealloc(line, oldlen+len+1,
			     "Failed to reallocate input line.");
	    strcat(line, lineBuffer);

	    oldlen += len;
	    if (line[oldlen-1] != '\\') break;
	    line[oldlen-1] = '\0';
	    oldlen--;
	}
    }
    return line;
}

static void HandleUnopenableUPRFile(filename, err)
    char *filename;
    int err;
{
    if (issueWarnings) {
	fprintf (stderr, "%s:  Could not open file %s (%s).\n",
		 program, filename, sys_errlist[err]);
    }

    if (strict) exit(1);
}


void PreprocessResourceDirectory(fullname)
    char *fullname;
{
    char *category;
    FILE *file;
    char *line;
    char *directoryPrefix = NULL;
    int noPrefix;

    file = fopen (fullname, "r");

    if (file == NULL) {
	HandleUnopenableUPRFile(fullname, errno);
	return;
    }

    /* Skip over list of categories */

    while (true) {
	if (fgets (lineBuffer, BUFFER_SIZE, file) == NULL) return;

	if (lineBuffer[0] == '.') break;
    }

    while (true) {
	/* Process category */
     
	line = GetWholeLine(file);
	if (line == NULL) {
	    if (directoryPrefix != NULL) free(directoryPrefix);
	    return;
	}
      
	if (line[0] == '/') { /* Handle optional directory prefix */
	    directoryPrefix = line;
	    continue;
	}

	category = line;

	while (true) {
	    char *resourceFile;

	    line = GetWholeLine(file);
	    if (line == NULL) {
		if (directoryPrefix != NULL) free(directoryPrefix);
		free(category);
		break;
	    }

	    if (line[0] == '.') {
		free(category);
		free(line);
		break;
	    }

	    resourceFile = line;
	    while (true) {
		if ((resourceFile = strchr(resourceFile, '=')) != NULL) {
		    if (resourceFile != line && *(resourceFile-1) != '\\') {
			*resourceFile++ = '\0';
			noPrefix = (*resourceFile == '=');
			if (noPrefix) resourceFile++;
			if (strcmp(category, "FontBDFSizes") == 0) {
			    AddUPRResourceBDF(line, resourceFile);
			} else if (strcmp(category, "FontFamily") == 0) {
			    AddUPRResourceFontFamily(line, resourceFile);
			} else AddUPRResource (category, line,
					       resourceFile,
					       (noPrefix ? NULL :
						           directoryPrefix),
					       noPrefix);
			break;
		    }
		    resourceFile++;
		} else break;	/* Bogus line */
	    }
	    free(line);
	}
    }
}

static int SkipToCharacter (file, character)
  FILE *file;
  char character;
{
  int c;

  while ((c = fgetc (file)) != EOF) {
    if (c == character)
      return (true);
  }

  return (false);
}

static int SkipToEitherCharacter (file, character1, character2, outchar)
  FILE *file;
  char character1, character2;
  char *outchar;
{
  register int c;

  while ((c = fgetc (file)) != EOF) {
    if (c == character1 || c == character2) {
	*outchar = c;
	return (true);
    }
  }

  return (false);
}


static void ProcessFont (file, fileName)
    FILE *file;
    char *fileName;
{
    char fontName[256], fontFamily[256], fontFullName[256], fontWeight[256];
    char key[256], buf[513];
    char familyReturn[256], fullnameReturn[256], faceReturn[256];
    char blendDesignPositions[256], blendDesignMap[256], blendAxisTypes[256];  
    char out;
    int found = 0;

    fontName[0] = fontFamily[0] = fontFullName[0] = fontWeight[0] = '\0';
    blendDesignPositions[0] = blendDesignMap[0] = blendAxisTypes[0] = '\0';  

    while (found != 0x7F && SkipToEitherCharacter (file, '/', 'e', &out)) {
	/* If we encounter an eexec, skip the rest of the file */
	if (out == 'e') {
	    if (fscanf (file, "%256s", key) != 1) continue;
	    if (strcmp(key, "exec") == 0) break;
	    continue;
	}

	if (fscanf (file, "%256s", key) != 1) continue;
	if (!SkipWhiteSpace(file)) break;
	if (!ReadItem(file, buf, 256)) break;

	if ((found & 0x1) == 0 && strcmp(key, "FullName") == 0) {
	    strcpy(fontFullName, buf);
	    found |= 0x1;
	    continue;
	}
	if ((found & 0x2) == 0 && strcmp(key, "FamilyName") == 0) {
	    strcpy(fontFamily, buf);
	    found |= 0x2;
	    continue;
	}
	if ((found & 0x4) == 0 && strcmp(key, "Weight") == 0) {
	    strcpy(fontWeight, buf);
	    found |= 0x4;
	    continue;
	}
	if ((found & 0x8) == 0 && strcmp(key, "FontName") == 0) {
	    strcpy(fontName, buf);
	    found |= 0x8;
	    continue;
	}
	if ((found & 0x10) == 0 && strcmp(key, "BlendDesignPositions") == 0) {
	    strcpy(blendDesignPositions, buf);
	    found |= 0x10;
	    continue;
	}
	if ((found & 0x20) == 0 && strcmp(key, "BlendDesignMap") == 0) {
	    strcpy(blendDesignMap, buf);
	    found |= 0x20;
	    continue;
	}
	if ((found & 0x40) == 0 && strcmp(key, "BlendAxisTypes") == 0) {
	    strcpy(blendAxisTypes, buf);
	    found |= 0x40;
	    continue;
	}
    }

    if (fontName[0] != '\0') {
	if (fontFullName[0] == '\0') {
	    if (fontFamily[0] != '\0') strcpy(fontFullName, fontFamily);
	    else strcpy(fontFullName, fontName);
	}
	if (fontFamily[0] == '\0') strcpy(fontFamily, fontFullName);

	MungeFontNames(fontName, fontFamily, fontFullName, fontWeight,
		       familyReturn, fullnameReturn, faceReturn);
#if DEBUG
	printf("Found font %s\n", fontName);
	printf("Munged to (%s) (%s)\n", familyReturn, faceReturn);
#endif
	sprintf(buf, "%s,%s", faceReturn, fontName);
	AddResource ("FontOutline", fontName, fileName, false);
	AddResource("FontFamily", familyReturn, buf, true);
	if (blendDesignPositions[0] != '\0' && blendDesignMap[0] != '\0' &&
	    blendAxisTypes[0] != '\0') {
#if DEBUG
	    printf("Font %s is multiple master\n", fontName);
#endif
	    AddResource("FontBlendPositions", fontName, blendDesignPositions, true);
	    AddResource("FontBlendMap", fontName, blendDesignMap, true);
	    AddResource("FontAxes", fontName, blendAxisTypes, true);
	}
    }
}


static void ProcessResource (file, fileName)
    FILE *file;
    char *fileName;
{
    char    resourceType[256];
    char    resourceName[256];
    char   *pointer;

    sscanf (lineBuffer, "%%!PS-Adobe-%*[0123456789.] Resource-%128s",
	    resourceType);

    StripName (resourceType);

    if (strcmp(resourceType, "Font") == 0) {
	ProcessFont(file, fileName);
	return;
    }

    pointer = FindKeyValue (file, "%%BeginResource");

    if (pointer == NULL) return;

    sscanf (pointer, "%*256s%256s", resourceName);
    StripName (resourceName);

    AddResource (resourceType, resourceName, fileName, false);
}


static void ProcessBDF (file, fileName)
    FILE *file;
    char *fileName;
{
    char    fontName[256];
    char    psFontName[256];
    char    key[256];
    unsigned int found = 0;
    char	  nameSize[300];
    int     resx, resy, size;
    char    sizebuf[50];

    fontName[0] = psFontName[0] = '\0';
    resx = resy = size = 0;

    while (SkipToCharacter(file, '\n')) {
	if (!SkipWhiteSpace(file)) break;
	if (fscanf (file, "%256s", key) != 1) continue;
	if (!SkipWhiteSpace(file)) break;

	if ((found & 1) == 0 && strcmp(key, "FONT") == 0) {
	    if (!ReadItem(file, fontName, 256)) break;
	    found |= 1;
	    continue;
	}
	if ((found & 2) == 0 && strcmp(key, "RESOLUTION_X") == 0) {
	    if (fscanf (file, "%d", &resx) != 1) break;
	    found |= 2;
	    continue;
	}
	if ((found & 4) == 0 && strcmp(key, "RESOLUTION_Y") == 0) {
	    if (fscanf (file, "%d", &resy) != 1) break;
	    found |= 4;
	    continue;
	}
	if ((found & 8) == 0 && strcmp(key, "_ADOBE_PSFONT") == 0) {
	    if (!ReadItem(file, psFontName, 256)) break;
	    found |= 8;
	    continue;
	}
	if ((found & 16) == 0 && strcmp(key, "SIZE") == 0) {
	    if (fscanf(file, "%d %d %d", &size, &resx, &resy) != 3) break;
	    found |= 16;
	    continue;
	}
	if ((found & 32) == 0 && strcmp(key, "POINT_SIZE") == 0) {
	    if (fscanf(file, "%d", &size) != 1) break;
	    size /= 10;
	    found |= 32;
	}
	if (strcmp(key, "ENDPROPERTIES") == 0) break;
	if (strcmp(key, "STARTCHAR") == 0) break;
    }
	
    if (psFontName[0] != '\0') strcpy(fontName, psFontName);

    if (size == 0 || fontName[0] == '\0') return;
    if (resx == 0 || resy == 0) sprintf(sizebuf, "%d", size);
    else sprintf(sizebuf, "%d-%d-%d", size, resx, resy);
	
    sprintf(nameSize, "%s%s", fontName, sizebuf);

    AddResource ("FontBDF", nameSize, fileName, false);
    AddResource ("FontBDFSizes", fontName, sizebuf, true);
}

static void ProcessAFM (file, fileName)
    FILE *file;
    char *fileName;
{
    char    fontName[256];
    char   *pointer;
    char   *extraCr;

    pointer = FindKeyValue (file, "FontName");

    if (pointer == NULL)
	    return;

    sscanf (pointer, "%256s", fontName);

    extraCr = strchr (fontName, '\r'); /* Handle DOS newlines */

    if (extraCr != NULL) *extraCr = '\0';

    AddResource ("FontAFM", fontName, fileName, false);
}

/* ARGSUSED */
static void ProcessResourceDirectory (file, fileName)
    FILE *file;
    char *fileName;
{
}

void MarkAsNonResource(filename)
    char *filename;
{
    AddResource("mkpsresPrivate", "NONRESOURCE", filename, false);
}

char *userCategories[] = {
    "Encoding",
    "File",
    "FontAFM",
    "FontBDF",
    "FontOutline",
    "FontPrebuilt",
    "Form",
    "Pattern",
    "ProcSet",
    NULL
};

static void IdentifyFromUser(filename, file)
    char *filename;
    FILE *file;
{
    int i, numCats, choice, size;
    char buf[256], name[256];
    static int stdinEOF = false;

    if (stdinEOF) return;

    if (file != NULL) rewind(file);

    while (1) {
	printf("Please indentify the file\n");
	printf("  0 - Not a resource file\n");
	i = 0;
	while (userCategories[i] != NULL) {
	    printf("  %d - %s\n", i+1, userCategories[i]);
	    i++;
	}
	numCats = i;
	printf("  %d - Other\n", numCats+1);
	if (file != NULL) printf("  %d - Show some of file\n", numCats+2);
	printf("> ");
	fflush(stdout);
	if (scanf("%d", &choice) != 1) {
	    stdinEOF = true;
	    return;
	}
	/* Skip the last of the number input line */
	if (fgets(buf, 256, stdin) == NULL) {
	    stdinEOF = true;
	    return;
	}
	if (choice < 0 || (file != NULL && choice > numCats+2) ||
	    (file == NULL && choice > numCats+1)) {
	    printf("Invalid choice\n\n");
	    continue;
	}
	if (choice == numCats+2) {
	    printf("\n");
	    for (i = 0; i < 10; i++) {
		if (fgets(buf, 256, file) != NULL) fputs(buf, stdout);
	    }
	    printf("\n");
	    continue;
	}
	if (choice == 0) {
	    MarkAsNonResource(filename);
	    return;
	}
	if (choice == numCats+1) {
	    printf("Please enter resource category:  ");
	    fflush(stdout);
	    if (fgets(buf, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (buf[0] == '\0') continue;
	    printf("Please enter resource name:  ");
	    fflush(stdout);
	    if (fgets(name, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(buf);
	    StripName(name);
	    AddResource(buf, name, filename, false);
	    return;
	}
	if (strcmp(userCategories[choice-1], "FontBDF") == 0) {
	    printf("Please enter font name:  ");
	    fflush(stdout);
	    if (fgets(name, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(name);
	    printf("Please enter font size:  ");
	    fflush(stdout);
	    if (scanf("%d", &size) != 1) {
		stdinEOF = true;
		return;
	    }
	    /* Skip the last of the number input line */
	    if (fgets(buf, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (size <= 0) continue;
	    sprintf(buf, "%d", size);
	    AddResource("FontBDFSizes", name, buf, false);
	    sprintf(buf, "%s%d", name, size);
	    AddResource("FontBDF", buf, filename, true);
	    return;
	}
	if (strcmp(userCategories[choice-1], "FontOutline") == 0) {
	    char family[256], face[256];
	    printf("Please enter font name:  ");
	    fflush(stdout);
	    if (fgets(name, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(name);
	    printf("Please enter font family:  ");
	    fflush(stdout);
	    if (fgets(family, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(family);
	    printf("Please enter font face:  ");
	    fflush(stdout);
	    if (fgets(face, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(face);
	    AddResource("FontOutline", name, filename, true);
	    sprintf(buf, "%s,%s", face, name);
	    AddResource("FontFamily", family, buf, false);
	    return;
	}

	printf("Please enter %s name:  ", userCategories[choice-1]);
	fflush(stdout);
	if (fgets(name, 256, stdin) == NULL) {
	    stdinEOF = true;
	    return;
	}
	if (name[0] == '\0') continue;
	StripName(name);
	AddResource(userCategories[choice-1], name, filename, true);
	return;
    }
}

int IdentifyFromUPRList(filename)
    char *filename;
{
    UPRResource *resource = UPRresources[Hash(filename)];

    while (resource != NULL && strcmp(filename, resource->file) != 0) {
	resource = resource->next;
    }
    if (resource == NULL) return false;
    AddResource(resource->category, resource->name, resource->file,
		resource->noPrefix);
    return true;
}

int IdentifyFromFileSuffix(fileName)
    char *fileName;
{
    int len, len1;
    char fontName[256];
    register char *ch;

    /* The only files we can get anything useful from without looking inside
       are AFM files and prebuilt files */

    len = strlen(fileName);
    if (len < 5) return false;
    if (strcmp(".afm", fileName + len - 4) == 0) {
	len1 = 0;
	for (ch = fileName+len-5; ch > fileName && *ch != '/'; ch--) len1++;
	if (*ch == '/') ch++;
	else len1++;
	strcpy(fontName, ch);
	fontName[len1] = '\0';
	AddResource("FontAFM", fontName, fileName, false);
	return true;
    }
    if (len < 6) return false;
    if ((strcmp(".bepf", fileName + len - 5) == 0) ||
	(strcmp(".lepf", fileName + len - 5) == 0)) {
	len1 = 0;
	for (ch = fileName+len-6; ch > fileName && *ch != '/'; ch--) len1++;
	if (*ch == '/') ch++;
	else len1++;
	strcpy(fontName, ch);
	fontName[len1] = '\0';
	AddResource("FontPrebuilt", fontName, fileName, false);
	return true;
    }
    return false;
}

static void HandleUnopenableFile(filename, err)
    char *filename;
    int err;
{
    if (IdentifyFromUPRList(filename)) return;

    if (!noSuffix && IdentifyFromFileSuffix(filename)) return;

    if (issueWarnings) {
	fprintf (stderr, "%s:  Could not open file %s (%s).\n",
		 program, filename, sys_errlist[err]);
    }

    if (strict) exit(1);

    if (interactive) IdentifyFromUser(filename, (FILE *) NULL);
    else MarkAsNonResource(filename);
}


static void HandleUnidentifiableFile(filename, file)
    char *filename;
    FILE *file;
{
    if (IdentifyFromUPRList(filename)) return;

    if (!noSuffix && IdentifyFromFileSuffix(filename)) return;

    if (issueWarnings) {
	fprintf (stderr, "%s:  Could not identify file %s.\n",
		 program, filename);
    }

    if (strict) exit(1);

    if (interactive) IdentifyFromUser(filename, file);
    else MarkAsNonResource(filename);
}


typedef struct {
    char *key;
    int   keyLength;
    char *key2;
    int	  key2Length;
    void (*proc)(/* FILE *file, char *fileName */);
} ResourceKey;

static ResourceKey resourceTypes[] = {
  {"%!PS-AdobeFont-",	15, NULL, 		 0, ProcessFont},
  {"%!PS-Adobe-",	11, " Resource-",	10, ProcessResource},
  {"STARTFONT",		 9, NULL,		 0, ProcessBDF},
  {"StartFontMetrics",	16, NULL,		 0, ProcessAFM},
  {"PS-Resources-",	13, NULL,		 0, ProcessResourceDirectory},
  {NULL,		 0, NULL}
};

static void ProcessFile (fileName, filePath)
  char *fileName;
  char  *filePath;
{
  FILE   *file;
  ResourceKey *resourceType;
  char	version[10];

  if (fileName[0] == '.')
    return;

  file = fopen (filePath, "r");

  if (file == NULL) {
      HandleUnopenableFile(filePath, errno);
      return;
  }

  fgets (lineBuffer, BUFFER_SIZE, file);

  for (resourceType = resourceTypes; resourceType->key != NULL;
       resourceType++) {
      if (strncmp (resourceType->key, lineBuffer,
		   resourceType->keyLength) == 0) {
	  if (resourceType->key2 == NULL) {
	      (*resourceType->proc) (file, filePath);
	      break;

	  } else {
	      if (sscanf(lineBuffer+resourceType->keyLength,
			 "%10[0123456789.]", version) != 1) continue;

	      if (strncmp(resourceType->key2,
			  lineBuffer + resourceType->keyLength +
				  strlen(version),
			  resourceType->key2Length) == 0) {
		  (*resourceType->proc) (file, filePath);
		  break;
	      }
	  }
      }
  }

  if (resourceType->key == NULL) HandleUnidentifiableFile(filePath, file);
  
  fclose (file);
}

static void ProcessUPRFile (fileName, filePath)
  char *fileName;
  char  *filePath;
{
  int len;

  if (fileName[0] == '.')
    return;

  len = strlen(fileName);
  if (len < 4) return;
  if (strcmp(".upr", fileName + len - 4) != 0) return;

  PreprocessResourceDirectory(filePath);
}


static void ProcessDirectory (directoryName, top, fileFunction)
  char *directoryName;
  int top;
  void (*fileFunction)();
{
  DIR    *directory;
#if NeXT
  struct direct *directoryEntry;
#else
  struct dirent *directoryEntry;
#endif
  struct stat status;
  char *filePath, *fileName;

#if DEBUG
  fprintf (stderr, "Directory: %s\n", directoryName);
#endif

  directory = opendir (directoryName);

  if (directory == NULL) {
      /* Treat top level failures to open differently from subdirectories */
      if (top || issueWarnings) {
	  fprintf (stderr, "%s:  Could not open directory %s (%s).\n",
		   program, directoryName, sys_errlist[errno]);
      }
      if (strict) exit(1);
      return;
  }
  while ((directoryEntry = readdir (directory)) != NULL) {
      fileName = directoryEntry->d_name;
      if (fileName[0] == '.') continue;
      filePath = ckmalloc (strlen (directoryName) + strlen (fileName) + 2,
		       "Failed to allocate file name string.");
      sprintf (filePath, "%s/%s", directoryName, fileName);
      if (stat(filePath, &status) == -1) {
	  if (issueWarnings) {
	      fprintf(stderr, "Couldn't get status of file %s (%s)\n",
		      filePath, sys_errlist[errno]);
	  }
	  if (strict) exit(1);
	  continue;
      }
      if (S_ISDIR(status.st_mode)) {
	  if (recursive) ProcessDirectory(filePath, false, fileFunction);
      } else (*fileFunction) (fileName, filePath);
      free (filePath);
  }

  closedir (directory);
}


void GenerateEntriesFromUPRList()
{
  Category *category;
  Resource *resource;
  UPRResource *upr;
  int i, bucket;

  for (category = categories; category != NULL; category = category->next) {
      if (strcmp(category->name, "FontBDFSizes") == 0 ||
	  strcmp(category->name, "FontFamily") == 0) {
	  continue;
      }

      for (resource = category->list; resource != NULL;
	   resource = resource->next) {
	  for (upr = UPRresources[Hash(resource->file)];
	       upr != NULL && strcmp(upr->file, resource->file) < 0;
	       upr = upr->next) {}
	  if (upr != NULL) upr->found = true;
      }

      if (category->hash != NULL) {
	  for (bucket = 0; bucket < HASHSIZE; bucket++) {
	      for (resource = category->hash[bucket]; resource != NULL;
		   resource = resource->next) {
		  for (upr = UPRresources[Hash(resource->file)];
		       upr != NULL && strcmp(upr->file, resource->file) < 0;
		       upr = upr->next) {}
		  if (upr != NULL) upr->found = true;
	      }
	  }
      }
  }

  for (bucket = 0; bucket < HASHSIZE; bucket++) {
      for (upr = UPRresources[bucket]; upr != NULL; upr = upr->next) {
	  if (upr->found) continue;

	  if (strcmp(upr->category, "FontBDFSizes") != 0 &&
	      strcmp(upr->category, "FontFamily") != 0) {

	      if (!keep) {
		  /* If this file is in one of the input dirs, but wasn't
		     found, it must have been deleted since the previous run */

		  for (i = 0; i < directoryCount; i++) {
		      if (strncmp(upr->file, directories[i],
				  directoryLen[i]) == 0 &&
			  upr->file[directoryLen[i]+1] == '/') goto NEXT_UPR;
		  }
	      }
	      AddResource(upr->category, upr->name, upr->file, upr->noPrefix);
	  }
NEXT_UPR:	;
      }
  }

  /* Now do BDFSizes and Families */

  for (bucket = 0; bucket < HASHSIZE; bucket++) {
      for (upr = UPRresources[bucket]; upr != NULL; upr = upr->next) {
	  if (upr->found) continue;

	  if (strcmp(upr->category, "FontBDFSizes") == 0) {
	      char *buf, *ch = upr->file;

	      /* We know there's a comma since we put one in.  Anything
		 before the comma is just there to make hashing work better */

	      while (*ch != '\0') ch++;
	      while (ch >= upr->file && *ch != ',') ch--;
	      if (*ch == ',') ch++;

	      buf = ckmalloc(strlen(upr->name) + strlen(ch) + 1,
			     "Failed to allocate BDF name\n");
	      strcpy(buf, upr->name);
	      strcat(buf, ch);

	      if (FindResource("FontBDF", buf)) {
		  AddResource(upr->category, upr->name, ch, false);
	      }
	      free(buf);

	  } else if (strcmp(upr->category, "FontFamily") == 0) {
	      char *ch = upr->file;

	      while (true) {
		  while (*ch != '\0' && *ch != ',') ch++;
		  if (*ch == '\0') break;
		  if (ch > upr->file && *(ch-1) == '\\') ch++;
		  else break;
	      }
	      if (*ch == ',') {
		  ch++;
		  if (FindResource("FontOutline", ch)) {
		      AddResource(upr->category, upr->name, upr->file, false);
		  }
	      }
	  }
      }
  }
}


static char *ExtractDirectoryPrefix()
{
  Category *category;
  Resource *resource;
  char *prefix = NULL;
  char *ch1, *ch2;
  int bucket;

  if (noPrefix) return NULL;

  category = categories;

  while (category != NULL) {
      if (strcmp(category->name, "FontBDFSizes") == 0 ||
	  strcmp(category->name, "FontFamily") == 0 ||
	  strcmp(category->name, "FontBlendPositions") == 0 ||
	  strcmp(category->name, "FontBlendMap") == 0 ||
	  strcmp(category->name, "FontAxes") == 0) {
	  category = category->next;
	  continue;
      }
      resource = category->list;
      
      while (resource != NULL) {
	  if (resource->noPrefix) {
	      resource = resource->next;
	      continue;
	  }
	  if (prefix == NULL) {
	      prefix = ckmalloc(strlen(resource->file) + 1,
				"Failed to allocate directory prefix");
	      strcpy(prefix, resource->file);
#if DEBUG
	      printf("New directory prefix: %s\n", prefix);
#endif
	  } else {
	      ch1 = prefix;
	      ch2 = resource->file;
	      while (*ch1 != '\0' && *ch2 != '\0' && *ch1 == *ch2) {
		  ch1++; ch2++;
	      }
#if DEBUG
	      if (*ch1 != '\0') {
		  *ch1 = '\0';
		  printf("New directory prefix: %s\n", prefix);
	      }
#endif
	      *ch1 = '\0';
	  }
	  resource = resource->next;
      }

      if (category->hash != NULL) {
	  for (bucket = 0; bucket < HASHSIZE; bucket++) {
	      for (resource = category->hash[bucket]; resource != NULL;
		   resource = resource->next) {

		  if (resource->noPrefix) continue;

		  if (prefix == NULL) {
		      prefix = ckmalloc(strlen(resource->file) + 1,
					"Failed to allocate directory prefix");
		      strcpy(prefix, resource->file);
#if DEBUG
		      printf("New directory prefix: %s\n", prefix);
#endif
		  } else {
		      ch1 = prefix;
		      ch2 = resource->file;
		      while (*ch1 != '\0' && *ch2 != '\0' && *ch1 == *ch2) {
			  ch1++; ch2++;
		      }
#if DEBUG
		      if (*ch1 != '\0') {
			  *ch1 = '\0';
			  printf("New directory prefix: %s\n", prefix);
		      }
#endif
		      *ch1 = '\0';
		  }
	      }
	  }
      }
      category = category->next;
  }
  if (prefix != NULL) {
      ch1 = ch2 = prefix;
      while (*ch1 != '\0') {
	  if (*ch1 == '/') ch2 = ch1;
	  ch1++;
      }
      *ch2 = '\0';
  }

  /* Prefixes must be absolute path names */
  if (prefix != NULL && prefix[0] != '/') prefix[0] = '\0';
  return prefix;
}


static void OutputChar(file, c)
    FILE *file;
    char c;
{
    static int len;	/* Rely upon being 0 initially */

    if (c == '\n') len = 0;
    else {
	len++;
	if (len == MAXLINELEN) {
	    fputs("\\\n", file);
	    len = 1;;
	}
    }
	
    putc(c, file);
}

static void OutputString(file, s)
    FILE *file;
    char *s;
{
    while (*s != '\0') OutputChar(file, *s++);
}


static void PrintResourceDirectory (directoryName, file)
  char *directoryName;
  FILE *file;
{
  Category *category;
  Resource *resource;
  char *pname;
  int prefixlen;
  int bucket;
#define outs(s) OutputString(file, s)
#define outc(c) OutputChar(file, c)

  if (directoryName == NULL || *directoryName == '\0') prefixlen = 0;
  else prefixlen = strlen(directoryName) + 1;

  if (makeExclusive) outs("PS-Resources-Exclusive-1.0\n");
  else outs("PS-Resources-1.0\n");

  category = categories;

  while (category != NULL) {
    outs(category->name);
    outc('\n');
    category = category->next;
  }

  outs(".\n");
  if (prefixlen > 1) {
      outc('/');
      outs(directoryName);
      outc('\n');
  }

  category = categories;

  while (category != NULL) {
    resource = category->list;
    outs(category->name);
    outc('\n');
    if (strcmp(category->name, "FontBDFSizes") != 0 &&
	strcmp(category->name, "FontFamily") != 0) {
	while (resource != NULL) {
	    outs(resource->name);
	    outc('=');
	    if (resource->noPrefix) {
		outc('=');
		outs(resource->file);
	    } else outs(resource->file+prefixlen);
	    outc('\n');
	    resource = resource->next;
	}
    } else {
	while (resource != NULL) {
	    outs(resource->name);
	    outc('=');
	    outs(resource->file);
	    pname = resource->name;
	    resource = resource->next;
	    while (resource != NULL && strcmp(resource->name, pname) == 0) {
		outc(',');
		outs(resource->file);
		resource = resource->next;
	    }
	    outc('\n');
	}
    }
    if (category->hash != NULL) {
	for (bucket = 0; bucket < HASHSIZE; bucket++) {
	    for (resource = category->hash[bucket]; resource != NULL;
		 resource = resource->next) {
		outs(resource->name);
		outc('=');
		if (resource->noPrefix) {
		    outc('=');
		    outs(resource->file);
		} else outs(resource->file+prefixlen);
		outc('\n');
	    }
	}
    }
    outs(".\n");
    category = category->next;
  }
#undef outs
#undef outc
}


void Usage()
{
    fprintf(stderr,
	   "Usage: %s [-o outputfile] [-f inputfile]... [-dir directory]...\n",
	    program);
    fprintf(stderr,
	   "        [-e] [-i] [-nr] [-s] [-p] [-d] [-k] [-q] directory...\n");
    exit(1);
}

int stdinDirectories = false;

void ReadStdinDirectories()
{
    char buf[256];

    if (stdinDirectories) {
	fprintf(stderr, "%s:  Can only read stdin as directory list once.\n",
		program);
	Usage();
    }

    stdinDirectories = true;

    while (scanf("%256s", buf) == 1) {
	directoryCount++;
	directories = (char **) ckrealloc((char *) directories,
				  directoryCount * sizeof(char *),
				  "Failed to reallocate directory list.");
	directories[directoryCount-1] = ckmalloc(strlen(buf)+1,
				  "Failed to allocate directory name.");
	strcpy(directories[directoryCount-1], buf);
    }
}

void ProcessArglist(argc, argv)
    int argc;
    char *argv[];
{
    int i = 1, j;

    if (argc > 0) {
	program = strrchr(argv[0], '/');
	if (program != NULL) program++;
	else program = argv[0];
    } else program = "makepsres";

    directories = (char **) ckmalloc(argc * sizeof(char *),
				     "Failed to allocate directory list.");
    while (i < argc) {
	if (strcmp(argv[i], "-help") == 0) Usage();

	else if (strcmp(argv[i], "-q") == 0) issueWarnings = false;

	else if (strcmp(argv[i], "-k") == 0) keep = true;

	else if (strcmp(argv[i], "-d") == 0) discard = true;

	else if (strcmp(argv[i], "-p") == 0) noPrefix = true;

	else if (strcmp(argv[i], "-s") == 0) strict = true;

	else if (strcmp(argv[i], "-nr") == 0) recursive = false;

	else if (strcmp(argv[i], "-nb") == 0) noBackup = true;

	else if (strcmp(argv[i], "-ns") == 0) noSuffix = true;

	else if (strcmp(argv[i], "-i") == 0) interactive = true;

	else if (strcmp(argv[i], "-e") == 0) makeExclusive = true;

	else if (strcmp(argv[i], "-f") == 0) {
	    i++;
	    if (i >= argc) Usage();
	    if (inputFiles == NULL) {
		inputFiles = (char **) ckmalloc(argc * sizeof(char *),
					"Failed to allocat input file list.");
	    }
	    inputFiles[inputCount++] = argv[i];
	}

	else if (strcmp(argv[i], "-o") == 0) {
	    i++;
	    if (i >= argc) Usage();
	    outputFilename = argv[i];
	}

	else if (strcmp(argv[i], "-dir") == 0) {
	    i++;
	    if (i >= argc) Usage();
	    directories[directoryCount++] = argv[i];
	}

	else directories[directoryCount++] = argv[i];

	i++;
    }

    if (directoryCount == 0) {
	directoryCount = 1;
	directories[0] = ".";
    } else {
	for (i = 0; i < directoryCount; i++) {
	    if (strcmp(directories[i], "-") == 0) ReadStdinDirectories();
	}
    }

    if (stdinDirectories) {
	j = 0;
	for (i = 0; i < directoryCount; i++) {
	    if (strcmp(directories[i], "-") != 0) {
		directories[j] = directories[i];
		j++;
	    }
	}
	directoryCount--;
    }

    for (i = 0; i < inputCount; i++) {
	if (strcmp(inputFiles[i], "-") == 0 && stdinDirectories) {
	    fprintf(stderr,
	    "%s:  Cannot read stdin as both directory list and input file\n",
		    program);
	    Usage();
	}
    }

#if DEBUG
    printf("Input directory list:\n");
    for (i = 0; i < directoryCount; i++) printf("  %s\n", directories[i]);
#endif

    directoryLen = (int *) ckmalloc(directoryCount * sizeof(int),
			    "Failed to allocate directory length list.");

    for (i = 0; i < directoryCount; i++) {
	directoryLen[i] = strlen(directories[i]);
    }
}


void CheckBackup(filename)
    char *filename;
{
    char *backupname;

    if (noBackup || strcmp(filename, "/dev/null") == 0) return;

    backupname = ckmalloc(strlen(filename)+2,
			  "Failed to allocate backup file name.'");
    strcpy(backupname, filename);
    strcat(backupname, "~");

    /* Effect "rename (filename, backupname)" in BSD/Sys-V independent way */

    (void) unlink (backupname); /* Ignore error */

    if (link (filename, backupname) != 0) {
	if (errno != ENOENT) {
	    fprintf(stderr, "%s:  Could not back up output file %s\n",
		    program, filename);
	    fprintf(stderr, "            and will not write over it (%s)\n",
		    sys_errlist[errno]);
	    exit(1);
	}
    } else (void) unlink(filename);
    free(backupname);
}


void IssueDuplicateWarnings()
{
    int headerIssued = false;
    Category *category;
    Duplicate *dup;

    for (category = categories; category != NULL; category = category->next) {
	if (category->duplicates == NULL) continue;
	if (!headerIssued) {
	    headerIssued = true;
	    fprintf(stderr, 
		    "%s:  Warning:  The output resource file contains the following\n",
		    program);
	    fprintf(stderr,
		    "            duplicates.  Edit them out if you wish.\n");
	}
	fprintf(stderr, "Category %s:\n", category->name);
	for (dup = category->duplicates; dup != NULL; dup = dup->next) {
	    fprintf(stderr, "  Resource:  %s\n", dup->name);
	    fprintf(stderr, "    First file:  %s\n", dup->file1);
	    fprintf(stderr, "    Second file:  %s\n", dup->file2);
	}
    }
}


main (argc, argv)
  int argc;
  char *argv[];
{
  FILE   *outputFile;
  int     i, len;
  char *prefix;

  directories = NULL;
  directoryCount = 0;
  recursive = true;
  discard = false;
  keep = false;
  outputFilename = NULL;
  inputFiles = NULL;
  inputCount = 0;
  makeExclusive = false;
  interactive = false;
  strict = false;
  noPrefix = false;
  issueWarnings = true;
  noBackup = false;
  noSuffix = false;

  ProcessArglist(argc, argv);

  for (i = 0; i < inputCount; i++) {
      PreprocessResourceDirectory(inputFiles[i]);
  }

  /* Make two passes; the first time we just look for .upr files, and the
     second time we look for everything.  This gives us a better chance at
     identifying files. */

  for (i = 0; i < directoryCount; i++) {
      len = strlen (directories[i]);
      if (directories[i][len - 1] == '/') directories[i][len - 1] = '\0';

      ProcessDirectory (directories[i], true, ProcessUPRFile);
  }

#if DEBUG
  {
      int i, biggestBucket, emptyCount;
      static int count[11];
      emptyCount = biggestBucket = 0;
      for (i = 0; i < HASHSIZE; i++) {
	  if (bucketCount[i] == 0) emptyCount++;
	  if (bucketCount[i] > biggestBucket) biggestBucket = bucketCount[i];
      }
      if (biggestBucket != 0) {
	  for (i = 0; i < HASHSIZE; i++) {
	      count[bucketCount[i] * 10 / biggestBucket]++;
	  }
      }
      printf("Total UPR entries: %d\n", totalHashed);
      printf("Buckets: %d\n", HASHSIZE);
      printf("Longest bucket: %d\n", biggestBucket);
      printf("Number of empty buckets: %d\n", emptyCount);
      if (biggestBucket != 0) {
	  for (i = 0; i < 10; i++) {
	      printf("Number of buckets <= %d entries: %d\n",
		     biggestBucket*(i+1) / 10, count[i]);
	  }
      }
  }
#endif

  for (i = 0; i < directoryCount; i++) {
      len = strlen (directories[i]);
      if (directories[i][len - 1] == '/') directories[i][len - 1] = '\0';

      ProcessDirectory (directories[i], true, ProcessFile);
  }

  if (outputFilename == NULL) outputFilename = "PSres.upr";

  if (strcmp(outputFilename, "-") == 0) outputFile = stdout;
  else {
      CheckBackup(outputFilename);
      outputFile = fopen (outputFilename, "w");
  }

  if (outputFile == NULL) {
      fprintf (stderr,
	       "%s: Failed to open %s for writing: %s\n",
	       program, outputFilename, sys_errlist[errno]);
      exit (1);
  }

  if (!discard) GenerateEntriesFromUPRList();

  prefix = ExtractDirectoryPrefix();
  PrintResourceDirectory (prefix, outputFile);
  IssueDuplicateWarnings();
  exit (0);
}
