// -*- C++ -*-
/* Copyright (C) 1989-2018 Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.com)

This file is part of groff.

groff is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or
(at your option) any later version.

groff is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include "driver.h"
#include "stringclass.h"
#include "cset.h"

#include "ps.h"

#ifdef NEED_DECLARATION_PUTENV
extern "C" {
  int putenv(const char *);
}
#endif /* NEED_DECLARATION_PUTENV */

#define GROPS_PROLOGUE "prologue"

static void print_ps_string(const string &s, FILE *outfp);

cset white_space("\n\r \t\f");
string an_empty_string;

char valid_input_table[256]= {
#ifndef IS_EBCDIC_HOST
  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,

  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
#else
  0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,

  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
#endif
};

const char *extension_table[] = {
  "DPS",
  "CMYK",
  "Composite",
  "FileSystem",
};

const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);

// this must stay in sync with 'resource_type' in 'ps.h'
const char *resource_table[] = {
  "font",
  "fontset",
  "procset",
  "file",
  "encoding",
  "form",
  "pattern",
};

const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);

static int read_uint_arg(const char **pp, unsigned *res)
{
  while (white_space(**pp))
    *pp += 1;
  if (**pp == '\0') {
    error("missing argument");
    return 0;
  }
  const char *start = *pp;
  // XXX use strtoul
  long n = strtol(start, (char **)pp, 10);
  if (n == 0 && *pp == start) {
    error("not an integer");
    return 0;
  }
  if (n < 0) {
    error("argument must not be negative");
    return 0;
  }
  *res = unsigned(n);
  return 1;
}

struct resource {
  resource *next;
  resource_type type;
  string name;
  enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 };
  unsigned flags;
  string version;
  unsigned revision;
  char *filename;
  int rank;
  resource(resource_type, string &, string & = an_empty_string, unsigned = 0);
  ~resource();
  void print_type_and_name(FILE *outfp);
};

resource::resource(resource_type t, string &n, string &v, unsigned r)
: next(0), type(t), flags(0), revision(r), filename(0), rank(-1)
{
  name.move(n);
  version.move(v);
  if (type == RESOURCE_FILE) {
    if (name.search('\0') >= 0)
      error("filename contains a character with code 0");
    filename = name.extract();
  }
}

resource::~resource()
{
  free(filename);
}

void resource::print_type_and_name(FILE *outfp)
{
  fputs(resource_table[type], outfp);
  putc(' ', outfp);
  print_ps_string(name, outfp);
  if (type == RESOURCE_PROCSET) {
    putc(' ', outfp);
    print_ps_string(version, outfp);
    fprintf(outfp, " %u", revision);
  }
}

resource_manager::resource_manager()
: extensions(0), language_level(0), resource_list(0)
{
  read_download_file();
  string procset_name("grops");
  extern const char *version_string;
  extern const char *revision_string;
  unsigned revision_uint;
  if (!read_uint_arg(&revision_string, &revision_uint))
    revision_uint = 0;
  string procset_version(version_string);
  procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name,
				     procset_version, revision_uint);
  procset_resource->flags |= resource::SUPPLIED;
}

resource_manager::~resource_manager()
{
  while (resource_list) {
    resource *tem = resource_list;
    resource_list = resource_list->next;
    delete tem;
  }
}

resource *resource_manager::lookup_resource(resource_type type,
					    string &name,
					    string &version,
					    unsigned revision)
{
  resource *r;
  for (r = resource_list; r; r = r->next)
    if (r->type == type
	&& r->name == name
	&& r->version == version
	&& r->revision == revision)
      return r;
  r = new resource(type, name, version, revision);
  r->next = resource_list;
  resource_list = r;
  return r;
}

// Just a specialized version of lookup_resource().

resource *resource_manager::lookup_font(const char *name)
{
  resource *r;
  for (r = resource_list; r; r = r->next)
    if (r->type == RESOURCE_FONT
	&& strlen(name) == (size_t)r->name.length()
	&& memcmp(name, r->name.contents(), r->name.length()) == 0)
      return r;
  string s(name);
  r = new resource(RESOURCE_FONT, s);
  r->next = resource_list;
  resource_list = r;
  return r;
}

void resource_manager::need_font(const char *name)
{
  lookup_font(name)->flags |= resource::FONT_NEEDED;
}

typedef resource *Presource;	// Work around g++ bug.

void resource_manager::document_setup(ps_output &out)
{
  int nranks = 0;
  resource *r;
  for (r = resource_list; r; r = r->next)
    if (r->rank >= nranks)
      nranks = r->rank + 1;
  if (nranks > 0) {
    // Sort resource_list in reverse order of rank.
    Presource *head = new Presource[nranks + 1];
    Presource **tail = new Presource *[nranks + 1];
    int i;
    for (i = 0; i < nranks + 1; i++) {
      head[i] = 0;
      tail[i] = &head[i];
    }
    for (r = resource_list; r; r = r->next) {
      i = r->rank < 0 ? 0 : r->rank + 1;
      *tail[i] = r;
      tail[i] = &(*tail[i])->next;
    }
    resource_list = 0;
    for (i = 0; i < nranks + 1; i++)
      if (head[i]) {
	*tail[i] = resource_list;
	resource_list = head[i];
      }
    a_delete head;
    a_delete tail;
    // check it
    for (r = resource_list; r; r = r->next)
      if (r->next)
	assert(r->rank >= r->next->rank);
    for (r = resource_list; r; r = r->next)
      if (r->type == RESOURCE_FONT && r->rank >= 0)
	supply_resource(r, -1, out.get_file());
  }
}

void resource_manager::print_resources_comment(unsigned flag, FILE *outfp)
{
  int continued = 0;
  for (resource *r = resource_list; r; r = r->next)
    if (r->flags & flag) {
      if (continued)
	fputs("%%+ ", outfp);
      else {
	fputs(flag == resource::NEEDED
	      ? "%%DocumentNeededResources: "
	      : "%%DocumentSuppliedResources: ",
	      outfp);
	continued = 1;
      }
      r->print_type_and_name(outfp);
      putc('\n', outfp);
    }
}

void resource_manager::print_header_comments(ps_output &out)
{
  for (resource *r = resource_list; r; r = r->next)
    if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED))
      supply_resource(r, 0, 0);
  print_resources_comment(resource::NEEDED, out.get_file());
  print_resources_comment(resource::SUPPLIED, out.get_file());
  print_language_level_comment(out.get_file());
  print_extensions_comment(out.get_file());
}

void resource_manager::output_prolog(ps_output &out)
{
  FILE *outfp = out.get_file();
  out.end_line();
  char *path;
  if (!getenv("GROPS_PROLOGUE")) {
    string e = "GROPS_PROLOGUE";
    e += '=';
    e += GROPS_PROLOGUE;
    e += '\0';
    if (putenv(strsave(e.contents())))
      fatal("putenv failed");
  }
  char *prologue = getenv("GROPS_PROLOGUE");
  FILE *fp = font::open_file(prologue, &path);
  if (!fp)
    fatal("can't find '%1'", prologue);
  fputs("%%BeginResource: ", outfp);
  procset_resource->print_type_and_name(outfp);
  putc('\n', outfp);
  process_file(-1, fp, path, outfp);
  fclose(fp);
  free(path);
  fputs("%%EndResource\n", outfp);
}

void resource_manager::import_file(const char *filename, ps_output &out)
{
  out.end_line();
  string name(filename);
  resource *r = lookup_resource(RESOURCE_FILE, name);
  supply_resource(r, -1, out.get_file(), 1);
}

void resource_manager::supply_resource(resource *r, int rank, FILE *outfp,
				       int is_document)
{
  if (r->flags & resource::BUSY) {
    r->name += '\0';
    fatal("loop detected in dependency graph for %1 '%2'",
	  resource_table[r->type],
	  r->name.contents());
  }
  r->flags |= resource::BUSY;
  if (rank > r->rank)
    r->rank = rank;
  char *path = 0;		// pacify compiler
  FILE *fp = 0;
  if (r->filename != 0) {
    if (r->type == RESOURCE_FONT) {
      fp = font::open_file(r->filename, &path);
      if (!fp) {
	error("can't find '%1'", r->filename);
	a_delete r->filename;
	r->filename = 0;
      }
    }
    else {
      errno = 0;
      fp = include_search_path.open_file_cautious(r->filename);
      if (!fp) {
	error("can't open '%1': %2", r->filename, strerror(errno));
	a_delete r->filename;
	r->filename = 0;
      }
      else
	path = r->filename;
    }
  }
  if (fp) {
    if (outfp) {
      if (r->type == RESOURCE_FILE && is_document) {
	fputs("%%BeginDocument: ", outfp);
	print_ps_string(r->name, outfp);
	putc('\n', outfp);
      }
      else {
	fputs("%%BeginResource: ", outfp);
	r->print_type_and_name(outfp);
	putc('\n', outfp);
      }
    }
    process_file(rank, fp, path, outfp);
    fclose(fp);
    if (r->type == RESOURCE_FONT)
      free(path);
    if (outfp) {
      if (r->type == RESOURCE_FILE && is_document)
	fputs("%%EndDocument\n", outfp);
      else
	fputs("%%EndResource\n", outfp);
    }
    r->flags |= resource::SUPPLIED;
  }
  else {
    if (outfp) {
      if (r->type == RESOURCE_FILE && is_document) {
	fputs("%%IncludeDocument: ", outfp);
	print_ps_string(r->name, outfp);
	putc('\n', outfp);
      }
      else {
	fputs("%%IncludeResource: ", outfp);
	r->print_type_and_name(outfp);
	putc('\n', outfp);
      }
    }
    r->flags |= resource::NEEDED;
  }
  r->flags &= ~resource::BUSY;
}

#define PS_MAGIC "%!PS-Adobe-"

static int ps_get_line(string &buf, FILE *fp)
{
  buf.clear();
  int c = getc(fp);
  if (c == EOF)
    return 0;
  current_lineno++;
  while (c != '\r' && c != '\n' && c != EOF) {
    if (!valid_input_table[c])
      error("invalid input character code %1", int(c));
    buf += c;
    c = getc(fp);
  }
  buf += '\n';
  buf += '\0';
  if (c == '\r') {
    c = getc(fp);
    if (c != EOF && c != '\n')
      ungetc(c, fp);
  }
  return 1;
}

static int read_text_arg(const char **pp, string &res)
{
  res.clear();
  while (white_space(**pp))
    *pp += 1;
  if (**pp == '\0') {
    error("missing argument");
    return 0;
  }
  if (**pp != '(') {
    for (; **pp != '\0' && !white_space(**pp); *pp += 1)
      res += **pp;
    return 1;
  }
  *pp += 1;
  res.clear();
  int level = 0;
  for (;;) {
    if (**pp == '\0' || **pp == '\r' || **pp == '\n') {
      error("missing ')'");
      return 0;
    }
    if (**pp == ')') {
      if (level == 0) {
	*pp += 1;
	break;
      }
      res += **pp;
      level--;
    }
    else if (**pp == '(') {
      level++;
      res += **pp;
    }
    else if (**pp == '\\') {
      *pp += 1;
      switch (**pp) {
      case 'n':
	res += '\n';
	break;
      case 'r':
	res += '\n';
	break;
      case 't':
	res += '\t';
	break;
      case 'b':
	res += '\b';
	break;
      case 'f':
	res += '\f';
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
	{
	  int val = **pp - '0';
	  if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
	    *pp += 1;
	    val = val*8 + (**pp - '0');
	    if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
	      *pp += 1;
	      val = val*8 + (**pp - '0');
	    }
	  }
	}
	break;
      default:
	res += **pp;
	break;
      }
    }
    else
      res += **pp;
    *pp += 1;
  }
  return 1;
}

resource *resource_manager::read_file_arg(const char **ptr)
{
  string arg;
  if (!read_text_arg(ptr, arg))
    return 0;
  return lookup_resource(RESOURCE_FILE, arg);
}

resource *resource_manager::read_font_arg(const char **ptr)
{
  string arg;
  if (!read_text_arg(ptr, arg))
    return 0;
  return lookup_resource(RESOURCE_FONT, arg);
}

resource *resource_manager::read_procset_arg(const char **ptr)
{
  string arg;
  if (!read_text_arg(ptr, arg))
    return 0;
  string version;
  if (!read_text_arg(ptr, version))
      return 0;
  unsigned revision;
  if (!read_uint_arg(ptr, &revision))
      return 0;
  return lookup_resource(RESOURCE_PROCSET, arg, version, revision);
}

resource *resource_manager::read_resource_arg(const char **ptr)
{
  while (white_space(**ptr))
    *ptr += 1;
  const char *name = *ptr;
  while (**ptr != '\0' && !white_space(**ptr))
    *ptr += 1;
  if (name == *ptr) {
    error("missing resource type");
    return 0;
  }
  int ri;
  for (ri = 0; ri < NRESOURCES; ri++)
    if (strlen(resource_table[ri]) == size_t(*ptr - name)
	&& strncasecmp(resource_table[ri], name, *ptr - name) == 0)
      break;
  if (ri >= NRESOURCES) {
    error("unknown resource type");
    return 0;
  }
  if (ri == RESOURCE_PROCSET)
    return read_procset_arg(ptr);
  string arg;
  if (!read_text_arg(ptr, arg))
    return 0;
  return lookup_resource(resource_type(ri), arg);
}

static const char *matches_comment(string &buf, const char *comment)
{
  if ((size_t)buf.length() < strlen(comment) + 3)
    return 0;
  if (buf[0] != '%' || buf[1] != '%')
    return 0;
  const char *bufp = buf.contents() + 2;
  for (; *comment; comment++, bufp++)
    if (*bufp != *comment)
      return 0;
  if (comment[-1] == ':')
    return bufp;
  if (*bufp == '\0' || white_space(*bufp))
    return bufp;
  return 0;
}

// Return 1 if the line should be copied out.

int resource_manager::do_begin_resource(const char *ptr, int, FILE *,
					FILE *)
{
  resource *r = read_resource_arg(&ptr);
  if (r)
    r->flags |= resource::SUPPLIED;
  return 1;
}

int resource_manager::do_include_resource(const char *ptr, int rank, FILE *,
					  FILE *outfp)
{
  resource *r = read_resource_arg(&ptr);
  if (r) {
    if (r->type == RESOURCE_FONT) {
      if (rank >= 0)
	supply_resource(r, rank + 1, outfp);
      else
	r->flags |= resource::FONT_NEEDED;
    }
    else
      supply_resource(r, rank, outfp);
  }
  return 0;
}

int resource_manager::do_begin_document(const char *ptr, int, FILE *,
					FILE *)
{
  resource *r = read_file_arg(&ptr);
  if (r)
    r->flags |= resource::SUPPLIED;
  return 1;
}

int resource_manager::do_include_document(const char *ptr, int rank, FILE *,
					  FILE *outfp)
{
  resource *r = read_file_arg(&ptr);
  if (r)
    supply_resource(r, rank, outfp, 1);
  return 0;
}

int resource_manager::do_begin_procset(const char *ptr, int, FILE *,
				       FILE *outfp)
{
  resource *r = read_procset_arg(&ptr);
  if (r) {
    r->flags |= resource::SUPPLIED;
    if (outfp) {
      fputs("%%BeginResource: ", outfp);
      r->print_type_and_name(outfp);
      putc('\n', outfp);
    }
  }
  return 0;
}

int resource_manager::do_include_procset(const char *ptr, int rank, FILE *,
					  FILE *outfp)
{
  resource *r = read_procset_arg(&ptr);
  if (r)
    supply_resource(r, rank, outfp);
  return 0;
}

int resource_manager::do_begin_file(const char *ptr, int, FILE *,
				    FILE *outfp)
{
  resource *r = read_file_arg(&ptr);
  if (r) {
    r->flags |= resource::SUPPLIED;
    if (outfp) {
      fputs("%%BeginResource: ", outfp);
      r->print_type_and_name(outfp);
      putc('\n', outfp);
    }
  }
  return 0;
}

int resource_manager::do_include_file(const char *ptr, int rank, FILE *,
				      FILE *outfp)
{
  resource *r = read_file_arg(&ptr);
  if (r)
    supply_resource(r, rank, outfp);
  return 0;
}

int resource_manager::do_begin_font(const char *ptr, int, FILE *,
				    FILE *outfp)
{
  resource *r = read_font_arg(&ptr);
  if (r) {
    r->flags |= resource::SUPPLIED;
    if (outfp) {
      fputs("%%BeginResource: ", outfp);
      r->print_type_and_name(outfp);
      putc('\n', outfp);
    }
  }
  return 0;
}

int resource_manager::do_include_font(const char *ptr, int rank, FILE *,
				      FILE *outfp)
{
  resource *r = read_font_arg(&ptr);
  if (r) {
    if (rank >= 0)
      supply_resource(r, rank + 1, outfp);
    else
      r->flags |= resource::FONT_NEEDED;
  }
  return 0;
}

int resource_manager::change_to_end_resource(const char *, int, FILE *,
					     FILE *outfp)
{
  if (outfp)
    fputs("%%EndResource\n", outfp);
  return 0;
}

int resource_manager::do_begin_preview(const char *, int, FILE *fp, FILE *)
{
  string buf;
  do {
    if (!ps_get_line(buf, fp)) {
      error("end of file in preview section");
      break;
    }
  } while (!matches_comment(buf, "EndPreview"));
  return 0;
}

int read_one_of(const char **ptr, const char **s, int n)
{
  while (white_space(**ptr))
    *ptr += 1;
  if (**ptr == '\0')
    return -1;
  const char *start = *ptr;
  do {
    ++(*ptr);
  } while (**ptr != '\0' && !white_space(**ptr));
  for (int i = 0; i < n; i++)
    if (strlen(s[i]) == size_t(*ptr - start)
	&& memcmp(s[i], start, *ptr - start) == 0)
      return i;
  return -1;
}

void skip_possible_newline(FILE *fp, FILE *outfp)
{
  int c = getc(fp);
  if (c == '\r') {
    current_lineno++;
    if (outfp)
      putc(c, outfp);
    int cc = getc(fp);
    if (cc != '\n') {
      if (cc != EOF)
	ungetc(cc, fp);
    }
    else {
      if (outfp)
	putc(cc, outfp);
    }
  }
  else if (c == '\n') {
    current_lineno++;
    if (outfp)
      putc(c, outfp);
  }
  else if (c != EOF)
    ungetc(c, fp);
}

int resource_manager::do_begin_data(const char *ptr, int, FILE *fp,
				    FILE *outfp)
{
  while (white_space(*ptr))
    ptr++;
  const char *start = ptr;
  unsigned numberof;
  if (!read_uint_arg(&ptr, &numberof))
    return 0;
  static const char *types[] = { "Binary", "Hex", "ASCII" };
  const int Binary = 0;
  int type = 0;
  static const char *units[] = { "Bytes", "Lines" };
  const int Bytes = 0;
  int unit = Bytes;
  while (white_space(*ptr))
    ptr++;
  if (*ptr != '\0') {
    type = read_one_of(&ptr, types, 3);
    if (type < 0) {
      error("bad data type");
      return 0;
    }
    while (white_space(*ptr))
      ptr++;
    if (*ptr != '\0') {
      unit = read_one_of(&ptr, units, 2);
      if (unit < 0) {
	error("expected 'Bytes' or 'Lines'");
	return 0;
      }
    }
  }
  if (type != Binary)
    return 1;
  if (outfp) {
    fputs("%%BeginData: ", outfp);
    fputs(start, outfp);
  }
  if (numberof > 0) {
    unsigned bytecount = 0;
    unsigned linecount = 0;
    do {
      int c = getc(fp);
      if (c == EOF) {
	error("end of file within data section");
	return 0;
      }
      if (outfp)
	putc(c, outfp);
      bytecount++;
      if (c == '\r') {
	int cc = getc(fp);
	if (cc != '\n') {
	  linecount++;
	  current_lineno++;
	}
	if (cc != EOF)
	  ungetc(cc, fp);
      }
      else if (c == '\n') {
	linecount++;
	current_lineno++;
      }
    } while ((unit == Bytes ? bytecount : linecount) < numberof);
  }
  skip_possible_newline(fp, outfp);
  string buf;
  if (!ps_get_line(buf, fp)) {
    error("missing %%%%EndData line");
    return 0;
  }
  if (!matches_comment(buf, "EndData"))
    error("bad %%%%EndData line");
  if (outfp)
    fputs(buf.contents(), outfp);
  return 0;
}

int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp,
				      FILE *outfp)
{
  if (!outfp)
    return 0;
  unsigned count;
  if (!read_uint_arg(&ptr, &count))
    return 0;
  if (outfp)
    fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count);
  while (count != 0) {
    int c = getc(fp);
    if (c == EOF) {
      error("end of file within binary section");
      return 0;
    }
    if (outfp)
      putc(c, outfp);
    --count;
    if (c == '\r') {
      int cc = getc(fp);
      if (cc != '\n')
	current_lineno++;
      if (cc != EOF)
	ungetc(cc, fp);
    }
    else if (c == '\n')
      current_lineno++;
  }
  skip_possible_newline(fp, outfp);
  string buf;
  if (!ps_get_line(buf, fp)) {
    error("missing %%%%EndBinary line");
    return 0;
  }
  if (!matches_comment(buf, "EndBinary")) {
    error("bad %%%%EndBinary line");
    if (outfp)
      fputs(buf.contents(), outfp);
  }
  else if (outfp)
    fputs("%%EndData\n", outfp);
  return 0;
}

static unsigned parse_extensions(const char *ptr)
{
  unsigned flags = 0;
  for (;;) {
    while (white_space(*ptr))
      ptr++;
    if (*ptr == '\0')
      break;
    const char *name = ptr;
    do {
      ++ptr;
    } while (*ptr != '\0' && !white_space(*ptr));
    int i;
    for (i = 0; i < NEXTENSIONS; i++)
      if (strlen(extension_table[i]) == size_t(ptr - name)
	  && memcmp(extension_table[i], name, ptr - name) == 0) {
	flags |= (1 << i);
	break;
      }
    if (i >= NEXTENSIONS) {
      string s(name, ptr - name);
      s += '\0';
      error("unknown extension '%1'", s.contents());
    }
  }
  return flags;
}

// XXX if it has not been surrounded with {Begin,End}Document need to strip
// out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections.

// XXX Perhaps the decision whether to use BeginDocument or
// BeginResource: file should be postponed till we have seen
// the first line of the file.

void resource_manager::process_file(int rank, FILE *fp, const char *filename,
				    FILE *outfp)
{
  // If none of these comments appear in the header section, and we are
  // just analyzing the file (ie outfp is 0), then we can return immediately.
  static const char *header_comment_table[] = {
    "DocumentNeededResources:",
    "DocumentSuppliedResources:",
    "DocumentNeededFonts:",
    "DocumentSuppliedFonts:",
    "DocumentNeededProcSets:",
    "DocumentSuppliedProcSets:",
    "DocumentNeededFiles:",
    "DocumentSuppliedFiles:",
  };
  
  const int NHEADER_COMMENTS = sizeof(header_comment_table)
			       / sizeof(header_comment_table[0]);
  struct comment_info {
    const char *name;
    int (resource_manager::*proc)(const char *, int, FILE *, FILE *);
  };

  static comment_info comment_table[] = {
    { "BeginResource:", &resource_manager::do_begin_resource },
    { "IncludeResource:", &resource_manager::do_include_resource },
    { "BeginDocument:", &resource_manager::do_begin_document },
    { "IncludeDocument:", &resource_manager::do_include_document },
    { "BeginProcSet:", &resource_manager::do_begin_procset },
    { "IncludeProcSet:", &resource_manager::do_include_procset },
    { "BeginFont:", &resource_manager::do_begin_font },
    { "IncludeFont:", &resource_manager::do_include_font },
    { "BeginFile:", &resource_manager::do_begin_file },
    { "IncludeFile:", &resource_manager::do_include_file },
    { "EndProcSet", &resource_manager::change_to_end_resource },
    { "EndFont", &resource_manager::change_to_end_resource },
    { "EndFile", &resource_manager::change_to_end_resource },
    { "BeginPreview:", &resource_manager::do_begin_preview },
    { "BeginData:", &resource_manager::do_begin_data },
    { "BeginBinary:", &resource_manager::do_begin_binary },
  };
  
  const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]);
  string buf;
  int saved_lineno = current_lineno;
  const char *saved_filename = current_filename;
  current_filename = filename;
  current_lineno = 0;
  if (!ps_get_line(buf, fp)) {
    current_filename = saved_filename;
    current_lineno = saved_lineno;
    return;
  }
  if ((size_t)buf.length() < sizeof(PS_MAGIC)
      || memcmp(buf.contents(), PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) {
    if (outfp) {
      do {
	if (!(broken_flags & STRIP_PERCENT_BANG)
	    || buf[0] != '%' || buf[1] != '!')
	  fputs(buf.contents(), outfp);
      } while (ps_get_line(buf, fp));
    }
  }
  else {
    if (!(broken_flags & STRIP_PERCENT_BANG) && outfp)
      fputs(buf.contents(), outfp);
    int in_header = 1;
    int interesting = 0;
    int had_extensions_comment = 0;
    int had_language_level_comment = 0;
    for (;;) {
      if (!ps_get_line(buf, fp))
	break;
      int copy_this_line = 1;
      if (buf[0] == '%') {
	if (buf[1] == '%') {
	  const char *ptr;
	  int i;
	  for (i = 0; i < NCOMMENTS; i++)
	    if ((ptr = matches_comment(buf, comment_table[i].name))) {
	      copy_this_line
		= (this->*(comment_table[i].proc))(ptr, rank, fp, outfp);
	      break;
	    }
	  if (i >= NCOMMENTS && in_header) {
	    if ((ptr = matches_comment(buf, "EndComments")))
	      in_header = 0;
	    else if (!had_extensions_comment
		     && (ptr = matches_comment(buf, "Extensions:"))) {
	      extensions |= parse_extensions(ptr);
	      // XXX handle possibility that next line is %%+
	      had_extensions_comment = 1;
	    }
	    else if (!had_language_level_comment
		     && (ptr = matches_comment(buf, "LanguageLevel:"))) {
	      unsigned ll;
	      if (read_uint_arg(&ptr, &ll) && ll > language_level)
		language_level = ll;
	      had_language_level_comment = 1;
	    }
	    else {
	      for (i = 0; i < NHEADER_COMMENTS; i++)
		if (matches_comment(buf, header_comment_table[i])) {
		  interesting = 1;
		  break;
		}
	    }
	  }
	  if ((broken_flags & STRIP_STRUCTURE_COMMENTS)
	      && (matches_comment(buf, "EndProlog")
		  || matches_comment(buf, "Page:")
		  || matches_comment(buf, "Trailer")))
	    copy_this_line = 0;
	}
	else if (buf[1] == '!') {
	  if (broken_flags & STRIP_PERCENT_BANG)
	    copy_this_line = 0;
	}
      }
      else
	in_header = 0;
      if (!outfp && !in_header && !interesting)
	break;
      if (copy_this_line && outfp)
	fputs(buf.contents(), outfp);
    }
  }
  current_filename = saved_filename;
  current_lineno = saved_lineno;
}

void resource_manager::read_download_file()
{
  char *path = 0;
  FILE *fp = font::open_file("download", &path);
  if (!fp)
    fatal("can't find 'download'");
  char buf[512];
  int lineno = 0;
  while (fgets(buf, sizeof(buf), fp)) {
    lineno++;
    char *p = strtok(buf, " \t\r\n");
    if (p == 0 || *p == '#')
      continue;
    char *q = strtok(0, " \t\r\n");
    if (!q)
      fatal_with_file_and_line(path, lineno, "missing filename");
    lookup_font(p)->filename = strsave(q);
  }
  free(path);
  fclose(fp);
}

// XXX Can we share some code with ps_output::put_string()?

static void print_ps_string(const string &s, FILE *outfp)
{
  int len = s.length();
  const char *str = s.contents();
  int funny = 0;
  if (str[0] == '(')
    funny = 1;
  else {
    for (int i = 0; i < len; i++)
      if (str[i] <= 040 || str[i] > 0176) {
	funny = 1;
	break;
      }
  }
  if (!funny) {
    put_string(s, outfp);
    return;
  }
  int level = 0;
  int i;
  for (i = 0; i < len; i++)
    if (str[i] == '(')
      level++;
    else if (str[i] == ')' && --level < 0)
      break;
  putc('(', outfp);
  for (i = 0; i < len; i++)
    switch (str[i]) {
    case '(':
    case ')':
      if (level != 0)
	putc('\\', outfp);
      putc(str[i], outfp);
      break;
    case '\\':
      fputs("\\\\", outfp);
      break;
    case '\n':
      fputs("\\n", outfp);
      break;
    case '\r':
      fputs("\\r", outfp);
      break;
    case '\t':
      fputs("\\t", outfp);
      break;
    case '\b':
      fputs("\\b", outfp);
      break;
    case '\f':
      fputs("\\f", outfp);
      break;
    default:
      if (str[i] < 040 || str[i] > 0176)
	fprintf(outfp, "\\%03o", str[i] & 0377);
      else
	putc(str[i], outfp);
      break;
    }
  putc(')', outfp);
}

void resource_manager::print_extensions_comment(FILE *outfp)
{
  if (extensions) {
    fputs("%%Extensions:", outfp);
    for (int i = 0; i < NEXTENSIONS; i++)
      if (extensions & (1 << i)) {
	putc(' ', outfp);
	fputs(extension_table[i], outfp);
      }
    putc('\n', outfp);
  }
}

void resource_manager::print_language_level_comment(FILE *outfp)
{
  if (language_level)
    fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);
}
