// -*- C++ -*-
/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005,
                 2006, 2007, 2009
   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/>. */

/*
 * PostScript documentation:
 *   http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
 *   http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf
 */

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

#include "ps.h"
#include <time.h>

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

extern "C" const char *Version_string;

// search path defaults to the current directory
search_path include_search_path(0, 0, 0, 1);

static int landscape_flag = 0;
static int manual_feed_flag = 0;
static int ncopies = 1;
static int linewidth = -1;
// Non-zero means generate PostScript code that guesses the paper
// length using the imageable area.
static int guess_flag = 0;
static double user_paper_length = 0;
static double user_paper_width = 0;

// Non-zero if -b was specified on the command line.
static int bflag = 0;
unsigned broken_flags = 0;

// Non-zero means we need the CMYK extension for PostScript Level 1
static int cmyk_flag = 0;

#define DEFAULT_LINEWIDTH 40	/* in ems/1000 */
#define MAX_LINE_LENGTH 72
#define FILL_MAX 1000

const char *const dict_name = "grops";
const char *const defs_dict_name = "DEFS";
const int DEFS_DICT_SPARE = 50;

double degrees(double r)
{
  return r*180.0/PI;
}

double radians(double d)
{
  return d*PI/180.0;
}

// This is used for testing whether a character should be output in the
// PostScript file using \nnn, so we really want the character to be
// less than 0200.

inline int is_ascii(char c)
{
  return (unsigned char)c < 0200;
}

ps_output::ps_output(FILE *f, int n)
: fp(f), col(0), max_line_length(n), need_space(0), fixed_point(0)
{
}

ps_output &ps_output::set_file(FILE *f)
{
  fp = f;
  col = 0;
  return *this;
}

ps_output &ps_output::copy_file(FILE *infp)
{
  int c;
  while ((c = getc(infp)) != EOF)
    putc(c, fp);
  return *this;
}

ps_output &ps_output::end_line()
{
  if (col != 0) {
    putc('\n', fp);
    col = 0;
    need_space = 0;
  }
  return *this;
}

ps_output &ps_output::special(const char *s)
{
  if (s == 0 || *s == '\0')
    return *this;
  if (col != 0) {
    putc('\n', fp);
    col = 0;
  }
  fputs(s, fp);
  if (strchr(s, '\0')[-1] != '\n')
    putc('\n', fp);
  need_space = 0;
  return *this;
}

ps_output &ps_output::simple_comment(const char *s)
{
  if (col != 0)
    putc('\n', fp);
  putc('%', fp);
  putc('%', fp);
  fputs(s, fp);
  putc('\n', fp);
  col = 0;
  need_space = 0;
  return *this;
}

ps_output &ps_output::begin_comment(const char *s)
{
  if (col != 0)
    putc('\n', fp);
  putc('%', fp);
  putc('%', fp);
  fputs(s, fp);
  col = 2 + strlen(s);
  return *this;
}

ps_output &ps_output::end_comment()
{
  if (col != 0) {
    putc('\n', fp);
    col = 0;
  }
  need_space = 0;
  return *this;
}

ps_output &ps_output::comment_arg(const char *s)
{
  int len = strlen(s);
  if (col + len + 1 > max_line_length) {
    putc('\n', fp);
    fputs("%%+", fp);
    col = 3;
  }
  putc(' ',  fp);
  fputs(s, fp);
  col += len + 1;
  return *this;
}

ps_output &ps_output::set_fixed_point(int n)
{
  assert(n >= 0 && n <= 10);
  fixed_point = n;
  return *this;
}

ps_output &ps_output::put_delimiter(char c)
{
  if (col + 1 > max_line_length) {
    putc('\n', fp);
    col = 0;
  }
  putc(c, fp);
  col++;
  need_space = 0;
  return *this;
}

ps_output &ps_output::put_string(const char *s, int n)
{
  int len = 0;
  int i;
  for (i = 0; i < n; i++) {
    char c = s[i];
    if (is_ascii(c) && csprint(c)) {
      if (c == '(' || c == ')' || c == '\\')
	len += 2;
      else
	len += 1;
    }
    else
      len += 4;
  }
  if (len > n*2) {
    if (col + n*2 + 2 > max_line_length && n*2 + 2 <= max_line_length) {
      putc('\n', fp);
      col = 0;
    }
    if (col + 1 > max_line_length) {
      putc('\n', fp);
      col = 0;
    }
    putc('<', fp);
    col++;
    for (i = 0; i < n; i++) {
      if (col + 2 > max_line_length) {
	putc('\n', fp);
	col = 0;
      }
      fprintf(fp, "%02x", s[i] & 0377);
      col += 2;
    }
    putc('>', fp);
    col++;
  }
  else {
    if (col + len + 2 > max_line_length && len + 2 <= max_line_length) {
      putc('\n', fp);
      col = 0;
    }
    if (col + 2 > max_line_length) {
      putc('\n', fp);
      col = 0;
    }
    putc('(', fp);
    col++;
    for (i = 0; i < n; i++) {
      char c = s[i];
      if (is_ascii(c) && csprint(c)) {
	if (c == '(' || c == ')' || c == '\\')
	  len = 2;
	else
	  len = 1;
      }
      else
	len = 4;
      if (col + len + 1 > max_line_length) {
	putc('\\', fp);
	putc('\n', fp);
	col = 0;
      }
      switch (len) {
      case 1:
	putc(c, fp);
	break;
      case 2:
	putc('\\', fp);
	putc(c, fp);
	break;
      case 4:
	fprintf(fp, "\\%03o", c & 0377);
	break;
      default:
	assert(0);
      }
      col += len;
    }
    putc(')', fp);
    col++;
  }
  need_space = 0;
  return *this;
}

ps_output &ps_output::put_number(int n)
{
  char buf[1 + INT_DIGITS + 1];
  sprintf(buf, "%d", n);
  int len = strlen(buf);
  if (col > 0 && col + len + need_space > max_line_length) {
    putc('\n', fp);
    col = 0;
    need_space = 0;
  }
  if (need_space) {
    putc(' ', fp);
    col++;
  }
  fputs(buf, fp);
  col += len;
  need_space = 1;
  return *this;
}

ps_output &ps_output::put_fix_number(int i)
{
  const char *p = if_to_a(i, fixed_point);
  int len = strlen(p);
  if (col > 0 && col + len + need_space > max_line_length) {
    putc('\n', fp);
    col = 0;
    need_space = 0;
  }
  if (need_space) {
    putc(' ', fp);
    col++;
  }
  fputs(p, fp);
  col += len;
  need_space = 1;
  return *this;
}

ps_output &ps_output::put_float(double d)
{
  char buf[128];
  sprintf(buf, "%.4f", d);
  int last = strlen(buf) - 1;
  while (buf[last] == '0')
    last--;
  if (buf[last] == '.')
    last--;
  buf[++last] = '\0';
  if (col > 0 && col + last + need_space > max_line_length) {
    putc('\n', fp);
    col = 0;
    need_space = 0;
  }
  if (need_space) {
    putc(' ', fp);
    col++;
  }
  fputs(buf, fp);
  col += last;
  need_space = 1;
  return *this;
}

ps_output &ps_output::put_symbol(const char *s)
{
  int len = strlen(s);
  if (col > 0 && col + len + need_space > max_line_length) {
    putc('\n', fp);
    col = 0;
    need_space = 0;
  }
  if (need_space) {
    putc(' ', fp);
    col++;
  }
  fputs(s, fp);
  col += len;
  need_space = 1;
  return *this;
}

ps_output &ps_output::put_color(unsigned int c)
{
  char buf[128];
  sprintf(buf, "%.3g", double(c) / color::MAX_COLOR_VAL);
  int len = strlen(buf);
  if (col > 0 && col + len + need_space > max_line_length) {
    putc('\n', fp);
    col = 0;
    need_space = 0;
  }
  if (need_space) {
    putc(' ', fp);
    col++;
  }
  fputs(buf, fp);
  col += len;
  need_space = 1;
  return *this;
}

ps_output &ps_output::put_literal_symbol(const char *s)
{
  int len = strlen(s);
  if (col > 0 && col + len + 1 > max_line_length) {
    putc('\n', fp);
    col = 0;
  }
  putc('/', fp);
  fputs(s, fp);
  col += len + 1;
  need_space = 1;
  return *this;
}

class ps_font : public font {
  ps_font(const char *);
public:
  int encoding_index;
  char *encoding;
  char *reencoded_name;
  ~ps_font();
  void handle_unknown_font_command(const char *command, const char *arg,
				   const char *filename, int lineno);
  static ps_font *load_ps_font(const char *);
};

ps_font *ps_font::load_ps_font(const char *s)
{
  ps_font *f = new ps_font(s);
  if (!f->load()) {
    delete f;
    return 0;
  }
  return f;
}

ps_font::ps_font(const char *nm)
: font(nm), encoding_index(-1), encoding(0), reencoded_name(0)
{
}

ps_font::~ps_font()
{
  a_delete encoding;
  a_delete reencoded_name;
}

void ps_font::handle_unknown_font_command(const char *command, const char *arg,
					  const char *filename, int lineno)
{
  if (strcmp(command, "encoding") == 0) {
    if (arg == 0)
      error_with_file_and_line(filename, lineno,
			       "`encoding' command requires an argument");
    else
      encoding = strsave(arg);
  }
}

static void handle_unknown_desc_command(const char *command, const char *arg,
					const char *filename, int lineno)
{
  if (strcmp(command, "broken") == 0) {
    if (arg == 0)
      error_with_file_and_line(filename, lineno,
			       "`broken' command requires an argument");
    else if (!bflag)
      broken_flags = atoi(arg);
  }
}

struct subencoding {
  font *p;
  unsigned int num;
  int idx;
  char *subfont;
  const char *glyphs[256];
  subencoding *next;

  subencoding(font *, unsigned int, int, subencoding *);
  ~subencoding();
};

subencoding::subencoding(font *f, unsigned int n, int ix, subencoding *s)
: p(f), num(n), idx(ix), subfont(0), next(s)
{
  for (int i = 0; i < 256; i++)
    glyphs[i] = 0;
}

subencoding::~subencoding()
{
  a_delete subfont;
}

struct style {
  font *f;
  subencoding *sub;
  int point_size;
  int height;
  int slant;
  style();
  style(font *, subencoding *, int, int, int);
  int operator==(const style &) const;
  int operator!=(const style &) const;
};

style::style() : f(0)
{
}

style::style(font *p, subencoding *s, int sz, int h, int sl)
: f(p), sub(s), point_size(sz), height(h), slant(sl)
{
}

int style::operator==(const style &s) const
{
  return (f == s.f
	  && sub == s.sub
	  && point_size == s.point_size
	  && height == s.height
	  && slant == s.slant);
}

int style::operator!=(const style &s) const
{
  return !(*this == s);
}

class ps_printer : public printer {
  FILE *tempfp;
  ps_output out;
  int res;
  glyph *space_glyph;
  int pages_output;
  int paper_length;
  int equalise_spaces;
  enum { SBUF_SIZE = 256 };
  char sbuf[SBUF_SIZE];
  int sbuf_len;
  int sbuf_start_hpos;
  int sbuf_vpos;
  int sbuf_end_hpos;
  int sbuf_space_width;
  int sbuf_space_count;
  int sbuf_space_diff_count;
  int sbuf_space_code;
  int sbuf_kern;
  style sbuf_style;
  color sbuf_color;		// the current PS color
  style output_style;
  subencoding *subencodings;
  int output_hpos;
  int output_vpos;
  int output_draw_point_size;
  int line_thickness;
  int output_line_thickness;
  unsigned char output_space_code;
  enum { MAX_DEFINED_STYLES = 50 };
  style defined_styles[MAX_DEFINED_STYLES];
  int ndefined_styles;
  int next_encoding_index;
  int next_subencoding_index;
  string defs;
  int ndefs;
  resource_manager rm;
  int invis_count;

  void flush_sbuf();
  void set_style(const style &);
  void set_space_code(unsigned char);
  int set_encoding_index(ps_font *);
  subencoding *set_subencoding(font *, glyph *, unsigned char *);
  char *get_subfont(subencoding *, const char *);
  void do_exec(char *, const environment *);
  void do_import(char *, const environment *);
  void do_def(char *, const environment *);
  void do_mdef(char *, const environment *);
  void do_file(char *, const environment *);
  void do_invis(char *, const environment *);
  void do_endinvis(char *, const environment *);
  void set_line_thickness_and_color(const environment *);
  void fill_path(const environment *);
  void encode_fonts();
  void encode_subfont(subencoding *);
  void define_encoding(const char *, int);
  void reencode_font(ps_font *);
  void set_color(color *, int = 0);

  const char *media_name();
  int media_width();
  int media_height();
  void media_set();

public:
  ps_printer(double);
  ~ps_printer();
  void set_char(glyph *, font *, const environment *, int, const char *);
  void draw(int, int *, int, const environment *);
  void begin_page(int);
  void end_page(int);
  void special(char *, const environment *, char);
  font *make_font(const char *);
  void end_of_line();
};

// `pl' is in inches
ps_printer::ps_printer(double pl)
: out(0, MAX_LINE_LENGTH),
  pages_output(0),
  sbuf_len(0),
  subencodings(0),
  output_hpos(-1),
  output_vpos(-1),
  line_thickness(-1),
  ndefined_styles(0),
  next_encoding_index(0),
  next_subencoding_index(0),
  ndefs(0),
  invis_count(0)
{
  tempfp = xtmpfile();
  out.set_file(tempfp);
  if (linewidth < 0)
    linewidth = DEFAULT_LINEWIDTH;
  if (font::hor != 1)
    fatal("horizontal resolution must be 1");
  if (font::vert != 1)
    fatal("vertical resolution must be 1");
  if (font::res % (font::sizescale*72) != 0)
    fatal("res must be a multiple of 72*sizescale");
  int r = font::res;
  int point = 0;
  while (r % 10 == 0) {
    r /= 10;
    point++;
  }
  res = r;
  out.set_fixed_point(point);
  space_glyph = name_to_glyph("space");
  if (pl == 0)
    paper_length = font::paperlength;
  else
    paper_length = int(pl * font::res + 0.5);
  if (paper_length == 0)
    paper_length = 11 * font::res;
  equalise_spaces = font::res >= 72000;
}

int ps_printer::set_encoding_index(ps_font *f)
{
  if (f->encoding_index >= 0)
    return f->encoding_index;
  for (font_pointer_list *p = font_list; p; p = p->next)
    if (p->p != f) {
      char *encoding = ((ps_font *)p->p)->encoding;
      int encoding_index = ((ps_font *)p->p)->encoding_index;
      if (encoding != 0 && encoding_index >= 0 
	  && strcmp(f->encoding, encoding) == 0) {
	return f->encoding_index = encoding_index;
      }
    }
  return f->encoding_index = next_encoding_index++;
}

subencoding *ps_printer::set_subencoding(font *f, glyph *g,
					 unsigned char *codep)
{
  unsigned int idx = f->get_code(g);
  *codep = idx % 256;
  unsigned int num = idx >> 8;
  if (num == 0)
    return 0;
  subencoding *p = 0;
  for (p = subencodings; p; p = p->next)
    if (p->p == f && p->num == num)
      break;
  if (p == 0)
    p = subencodings = new subencoding(f, num, next_subencoding_index++,
				       subencodings);
  p->glyphs[*codep] = f->get_special_device_encoding(g);
  return p;
}

char *ps_printer::get_subfont(subencoding *sub, const char *stem)
{
  assert(sub != 0);
  if (!sub->subfont) {
    char *tem = new char[strlen(stem) + 2 + INT_DIGITS + 1];
    sprintf(tem, "%s@@%d", stem, sub->idx);
    sub->subfont = tem;
  }
  return sub->subfont;
}

void ps_printer::set_char(glyph *g, font *f, const environment *env, int w,
			  const char *)
{
  if (g == space_glyph || invis_count > 0)
    return;
  unsigned char code;
  subencoding *sub = set_subencoding(f, g, &code);
  style sty(f, sub, env->size, env->height, env->slant);
  if (sty.slant != 0) {
    if (sty.slant > 80 || sty.slant < -80) {
      error("silly slant `%1' degrees", sty.slant);
      sty.slant = 0;
    }
  }
  if (sbuf_len > 0) {
    if (sbuf_len < SBUF_SIZE
	&& sty == sbuf_style
	&& sbuf_vpos == env->vpos
	&& sbuf_color == *env->col) {
      if (sbuf_end_hpos == env->hpos) {
	sbuf[sbuf_len++] = code;
	sbuf_end_hpos += w + sbuf_kern;
	return;
      }
      if (sbuf_len == 1 && sbuf_kern == 0) {
	sbuf_kern = env->hpos - sbuf_end_hpos;
	sbuf_end_hpos = env->hpos + sbuf_kern + w;
	sbuf[sbuf_len++] = code;
	return;
      }
      /* If sbuf_end_hpos - sbuf_kern == env->hpos, we are better off
	 starting a new string. */
      if (sbuf_len < SBUF_SIZE - 1 && env->hpos >= sbuf_end_hpos
	  && (sbuf_kern == 0 || sbuf_end_hpos - sbuf_kern != env->hpos)) {
	if (sbuf_space_code < 0) {
	  if (f->contains(space_glyph) && !sub) {
	    sbuf_space_code = f->get_code(space_glyph);
	    sbuf_space_width = env->hpos - sbuf_end_hpos;
	    sbuf_end_hpos = env->hpos + w + sbuf_kern;
	    sbuf[sbuf_len++] = sbuf_space_code;
	    sbuf[sbuf_len++] = code;
	    sbuf_space_count++;
	    return;
	  }
	}
	else {
	  int diff = env->hpos - sbuf_end_hpos - sbuf_space_width;
	  if (diff == 0 || (equalise_spaces && (diff == 1 || diff == -1))) {
	    sbuf_end_hpos = env->hpos + w + sbuf_kern;
	    sbuf[sbuf_len++] = sbuf_space_code;
	    sbuf[sbuf_len++] = code;
	    sbuf_space_count++;
	    if (diff == 1)
	      sbuf_space_diff_count++;
	    else if (diff == -1)
	      sbuf_space_diff_count--;
	    return;
	  }
	}
      }
    }
    flush_sbuf();
  }
  sbuf_len = 1;
  sbuf[0] = code;
  sbuf_end_hpos = env->hpos + w;
  sbuf_start_hpos = env->hpos;
  sbuf_vpos = env->vpos;
  sbuf_style = sty;
  sbuf_space_code = -1;
  sbuf_space_width = 0;
  sbuf_space_count = sbuf_space_diff_count = 0;
  sbuf_kern = 0;
  if (sbuf_color != *env->col)
    set_color(env->col);
}

static char *make_encoding_name(int encoding_index)
{
  static char buf[3 + INT_DIGITS + 1];
  sprintf(buf, "ENC%d", encoding_index);
  return buf;
}

static char *make_subencoding_name(int subencoding_index)
{
  static char buf[6 + INT_DIGITS + 1];
  sprintf(buf, "SUBENC%d", subencoding_index);
  return buf;
}

const char *const WS = " \t\n\r";

void ps_printer::define_encoding(const char *encoding, int encoding_index)
{
  char *vec[256];
  int i;
  for (i = 0; i < 256; i++)
    vec[i] = 0;
  char *path;
  FILE *fp = font::open_file(encoding, &path);
  if (fp == 0)
    fatal("can't open encoding file `%1'", encoding);
  int lineno = 1;
  const int BUFFER_SIZE = 512;
  char buf[BUFFER_SIZE];
  while (fgets(buf, BUFFER_SIZE, fp) != 0) {
    char *p = buf;
    while (csspace(*p))
      p++;
    if (*p != '#' && *p != '\0' && (p = strtok(buf, WS)) != 0) {
      char *q = strtok(0, WS);
      int n = 0;		// pacify compiler
      if (q == 0 || sscanf(q, "%d", &n) != 1 || n < 0 || n >= 256)
	fatal_with_file_and_line(path, lineno, "bad second field");
      vec[n] = new char[strlen(p) + 1];
      strcpy(vec[n], p);
    }
    lineno++;
  }
  a_delete path;
  out.put_literal_symbol(make_encoding_name(encoding_index))
     .put_delimiter('[');
  for (i = 0; i < 256; i++) {
    if (vec[i] == 0)
      out.put_literal_symbol(".notdef");
    else {
      out.put_literal_symbol(vec[i]);
      a_delete vec[i];
    }
  }
  out.put_delimiter(']')
     .put_symbol("def");
  fclose(fp);
}

void ps_printer::reencode_font(ps_font *f)
{
  out.put_literal_symbol(f->reencoded_name)
     .put_symbol(make_encoding_name(f->encoding_index))
     .put_literal_symbol(f->get_internal_name())
     .put_symbol("RE");
}

void ps_printer::encode_fonts()
{
  if (next_encoding_index == 0)
    return;
  char *done_encoding = new char[next_encoding_index];
  for (int i = 0; i < next_encoding_index; i++)
    done_encoding[i] = 0;
  for (font_pointer_list *f = font_list; f; f = f->next) {
    int encoding_index = ((ps_font *)f->p)->encoding_index;
    if (encoding_index >= 0) {
      assert(encoding_index < next_encoding_index);
      if (!done_encoding[encoding_index]) {
	done_encoding[encoding_index] = 1;
	define_encoding(((ps_font *)f->p)->encoding, encoding_index);
      }
      reencode_font((ps_font *)f->p);
    }
  }
  a_delete done_encoding;
}

void ps_printer::encode_subfont(subencoding *sub)
{
  assert(sub->glyphs != 0);
  out.put_literal_symbol(make_subencoding_name(sub->idx))
     .put_delimiter('[');
  for (int i = 0; i < 256; i++)
  {
    if (sub->glyphs[i])
      out.put_literal_symbol(sub->glyphs[i]);
    else
      out.put_literal_symbol(".notdef");
  }
  out.put_delimiter(']')
     .put_symbol("def");
}

void ps_printer::set_style(const style &sty)
{
  char buf[1 + INT_DIGITS + 1];
  for (int i = 0; i < ndefined_styles; i++)
    if (sty == defined_styles[i]) {
      sprintf(buf, "F%d", i);
      out.put_symbol(buf);
      return;
    }
  if (ndefined_styles >= MAX_DEFINED_STYLES)
    ndefined_styles = 0;
  sprintf(buf, "F%d", ndefined_styles);
  out.put_literal_symbol(buf);
  const char *psname = sty.f->get_internal_name();
  if (psname == 0)
    fatal("no internalname specified for font `%1'", sty.f->get_name());
  char *encoding = ((ps_font *)sty.f)->encoding;
  if (sty.sub == 0) {
    if (encoding != 0) {
      char *s = ((ps_font *)sty.f)->reencoded_name;
      if (s == 0) {
	int ei = set_encoding_index((ps_font *)sty.f);
	char *tem = new char[strlen(psname) + 1 + INT_DIGITS + 1];
	sprintf(tem, "%s@%d", psname, ei);
	psname = tem;
	((ps_font *)sty.f)->reencoded_name = tem;
      }
      else
        psname = s;
    }
  }
  else
    psname = get_subfont(sty.sub, psname);
  out.put_fix_number((font::res/(72*font::sizescale))*sty.point_size);
  if (sty.height != 0 || sty.slant != 0) {
    int h = sty.height == 0 ? sty.point_size : sty.height;
    h *= font::res/(72*font::sizescale);
    int c = int(h*tan(radians(sty.slant)) + .5);
    out.put_fix_number(c)
       .put_fix_number(h)
       .put_literal_symbol(psname)
       .put_symbol("MF");
  }
  else {
    out.put_literal_symbol(psname)
       .put_symbol("SF");
  }
  defined_styles[ndefined_styles++] = sty;
}

void ps_printer::set_color(color *col, int fill)
{
  sbuf_color = *col;
  unsigned int components[4];
  char s[3];
  color_scheme cs = col->get_components(components);
  s[0] = fill ? 'F' : 'C';
  s[2] = 0;
  switch (cs) {
  case DEFAULT:			// black
    out.put_symbol("0");
    s[1] = 'g';
    break;
  case RGB:
    out.put_color(Red)
       .put_color(Green)
       .put_color(Blue);
    s[1] = 'r';
    break;
  case CMY:
    col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black);
    // fall through
  case CMYK:
    out.put_color(Cyan)
       .put_color(Magenta)
       .put_color(Yellow)
       .put_color(Black);
    s[1] = 'k';
    cmyk_flag = 1;
    break;
  case GRAY:
    out.put_color(Gray);
    s[1] = 'g';
    break;
  }
  out.put_symbol(s);
}

void ps_printer::set_space_code(unsigned char c)
{
  out.put_literal_symbol("SC")
     .put_number(c)
     .put_symbol("def");
}

void ps_printer::end_of_line()
{
  flush_sbuf();
  // this ensures that we do an absolute motion to the beginning of a line
  output_vpos = output_hpos = -1;
}

void ps_printer::flush_sbuf()
{
  enum {
    NONE,
    RELATIVE_H,
    RELATIVE_V,
    RELATIVE_HV,
    ABSOLUTE
    } motion = NONE;
  int space_flag = 0;
  if (sbuf_len == 0)
    return;
  if (output_style != sbuf_style) {
    set_style(sbuf_style);
    output_style = sbuf_style;
  }
  int extra_space = 0;
  if (output_hpos < 0 || output_vpos < 0)
    motion = ABSOLUTE;
  else {
    if (output_hpos != sbuf_start_hpos)
      motion = RELATIVE_H;
    if (output_vpos != sbuf_vpos) {
      if  (motion != NONE)
	motion = RELATIVE_HV;
      else
	motion = RELATIVE_V;
    }
  }
  if (sbuf_space_code >= 0) {
    int w = sbuf_style.f->get_width(space_glyph, sbuf_style.point_size);
    if (w + sbuf_kern != sbuf_space_width) {
      if (sbuf_space_code != output_space_code) {
	set_space_code(sbuf_space_code);
	output_space_code = sbuf_space_code;
      }
      space_flag = 1;
      extra_space = sbuf_space_width - w - sbuf_kern;
      if (sbuf_space_diff_count > sbuf_space_count/2)
	extra_space++;
      else if (sbuf_space_diff_count < -(sbuf_space_count/2))
	extra_space--;
    }
  }
  if (space_flag)
    out.put_fix_number(extra_space);
  if (sbuf_kern != 0)
    out.put_fix_number(sbuf_kern);
  out.put_string(sbuf, sbuf_len);
  char command_array[] = {'A', 'B', 'C', 'D',
			  'E', 'F', 'G', 'H',
			  'I', 'J', 'K', 'L',
			  'M', 'N', 'O', 'P',
			  'Q', 'R', 'S', 'T'};
  char sym[2];
  sym[0] = command_array[motion*4 + space_flag + 2*(sbuf_kern != 0)];
  sym[1] = '\0';
  switch (motion) {
  case NONE:
    break;
  case ABSOLUTE:
    out.put_fix_number(sbuf_start_hpos)
       .put_fix_number(sbuf_vpos);
    break;
  case RELATIVE_H:
    out.put_fix_number(sbuf_start_hpos - output_hpos);
    break;
  case RELATIVE_V:
    out.put_fix_number(sbuf_vpos - output_vpos);
    break;
  case RELATIVE_HV:
    out.put_fix_number(sbuf_start_hpos - output_hpos)
       .put_fix_number(sbuf_vpos - output_vpos);
    break;
  default:
    assert(0);
  }
  out.put_symbol(sym);
  output_hpos = sbuf_end_hpos;
  output_vpos = sbuf_vpos;
  sbuf_len = 0;
}

void ps_printer::set_line_thickness_and_color(const environment *env)
{
  if (line_thickness < 0) {
    if (output_draw_point_size != env->size) {
      // we ought to check for overflow here
      int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
      out.put_fix_number(lw)
	 .put_symbol("LW");
      output_draw_point_size = env->size;
      output_line_thickness = -1;
    }
  }
  else {
    if (output_line_thickness != line_thickness) {
      out.put_fix_number(line_thickness)
	 .put_symbol("LW");
      output_line_thickness = line_thickness;
      output_draw_point_size = -1;
    }
  }
  if (sbuf_color != *env->col)
    set_color(env->col);
}

void ps_printer::fill_path(const environment *env)
{
  if (sbuf_color == *env->fill)
    out.put_symbol("FL");
  else
    set_color(env->fill, 1);
}

void ps_printer::draw(int code, int *p, int np, const environment *env)
{
  if (invis_count > 0)
    return;
  flush_sbuf();
  int fill_flag = 0;
  switch (code) {
  case 'C':
    fill_flag = 1;
    // fall through
  case 'c':
    // troff adds an extra argument to C
    if (np != 1 && !(code == 'C' && np == 2)) {
      error("1 argument required for circle");
      break;
    }
    out.put_fix_number(env->hpos + p[0]/2)
       .put_fix_number(env->vpos)
       .put_fix_number(p[0]/2)
       .put_symbol("DC");
    if (fill_flag)
      fill_path(env);
    else {
      set_line_thickness_and_color(env);
      out.put_symbol("ST");
    }
    break;
  case 'l':
    if (np != 2) {
      error("2 arguments required for line");
      break;
    }
    set_line_thickness_and_color(env);
    out.put_fix_number(p[0] + env->hpos)
       .put_fix_number(p[1] + env->vpos)
       .put_fix_number(env->hpos)
       .put_fix_number(env->vpos)
       .put_symbol("DL");
    break;
  case 'E':
    fill_flag = 1;
    // fall through
  case 'e':
    if (np != 2) {
      error("2 arguments required for ellipse");
      break;
    }
    out.put_fix_number(p[0])
       .put_fix_number(p[1])
       .put_fix_number(env->hpos + p[0]/2)
       .put_fix_number(env->vpos)
       .put_symbol("DE");
    if (fill_flag)
      fill_path(env);
    else {
      set_line_thickness_and_color(env);
      out.put_symbol("ST");
    }
    break;
  case 'P':
    fill_flag = 1;
    // fall through
  case 'p':
    {
      if (np & 1) {
	error("even number of arguments required for polygon");
	break;
      }
      if (np == 0) {
	error("no arguments for polygon");
	break;
      }
      out.put_fix_number(env->hpos)
	 .put_fix_number(env->vpos)
	 .put_symbol("MT");
      for (int i = 0; i < np; i += 2)
	out.put_fix_number(p[i])
	   .put_fix_number(p[i+1])
	   .put_symbol("RL");
      out.put_symbol("CL");
      if (fill_flag)
	fill_path(env);
      else {
	set_line_thickness_and_color(env);
	out.put_symbol("ST");
      }
      break;
    }
  case '~':
    {
      if (np & 1) {
	error("even number of arguments required for spline");
	break;
      }
      if (np == 0) {
	error("no arguments for spline");
	break;
      }
      out.put_fix_number(env->hpos)
	 .put_fix_number(env->vpos)
	 .put_symbol("MT");
      out.put_fix_number(p[0]/2)
	 .put_fix_number(p[1]/2)
	 .put_symbol("RL");
      /* tnum/tden should be between 0 and 1; the closer it is to 1
	 the tighter the curve will be to the guiding lines; 2/3
	 is the standard value */
      const int tnum = 2;
      const int tden = 3;
      for (int i = 0; i < np - 2; i += 2) {
	out.put_fix_number((p[i]*tnum)/(2*tden))
	   .put_fix_number((p[i + 1]*tnum)/(2*tden))
	   .put_fix_number(p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden))
	   .put_fix_number(p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden))
	   .put_fix_number((p[i] - p[i]/2) + p[i + 2]/2)
	   .put_fix_number((p[i + 1] - p[i + 1]/2) + p[i + 3]/2)
	   .put_symbol("RC");
      }
      out.put_fix_number(p[np - 2] - p[np - 2]/2)
	 .put_fix_number(p[np - 1] - p[np - 1]/2)
	 .put_symbol("RL");
      set_line_thickness_and_color(env);
      out.put_symbol("ST");
    }
    break;
  case 'a':
    {
      if (np != 4) {
	error("4 arguments required for arc");
	break;
      }
      set_line_thickness_and_color(env);
      double c[2];
      if (adjust_arc_center(p, c))
	out.put_fix_number(env->hpos + int(c[0]))
	   .put_fix_number(env->vpos + int(c[1]))
	   .put_fix_number(int(sqrt(c[0]*c[0] + c[1]*c[1])))
	   .put_float(degrees(atan2(-c[1], -c[0])))
	   .put_float(degrees(atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])))
	   .put_symbol("DA");
      else
	out.put_fix_number(p[0] + p[2] + env->hpos)
	   .put_fix_number(p[1] + p[3] + env->vpos)
	   .put_fix_number(env->hpos)
	   .put_fix_number(env->vpos)
	   .put_symbol("DL");
    }
    break;
  case 't':
    if (np == 0)
      line_thickness = -1;
    else {
      // troff gratuitously adds an extra 0
      if (np != 1 && np != 2) {
	error("0 or 1 argument required for thickness");
	break;
      }
      line_thickness = p[0];
    }
    break;
  default:
    error("unrecognised drawing command `%1'", char(code));
    break;
  }
  output_hpos = output_vpos = -1;
}

const char *ps_printer::media_name()
{
  return "Default";
}

int ps_printer::media_width()
{
  /*
   *  NOTE:
   *  Although paper size is defined as real numbers, it seems to be
   *  a common convention to round to the nearest postscript unit.
   *  For example, a4 is really 595.276 by 841.89 but we use 595 by 842.
   *
   *  This is probably a good compromise, especially since the
   *  Postscript definition specifies that media
   *  matching should be done within a tolerance of 5 units.
   */
  return int(user_paper_width ? user_paper_width*72.0 + 0.5
			      : font::paperwidth*72.0/font::res + 0.5);
}

int ps_printer::media_height()
{
  return int(user_paper_length ? user_paper_length*72.0 + 0.5
			       : paper_length*72.0/font::res + 0.5);
}

void ps_printer::media_set()
{
  /*
   *  The setpagedevice implies an erasepage and initgraphics, and
   *  must thus precede any descriptions for a particular page.
   *
   *  NOTE:
   *  This does not work with ps2pdf when there are included eps
   *  segments that contain PageSize/setpagedevice.
   *  This might be a bug in ghostscript -- must be investigated.
   *  Using setpagedevice in an .eps is really the wrong concept, anyway.
   *
   *  NOTE:
   *  For the future, this is really the place to insert other
   *  media selection features, like:
   *    MediaColor
   *    MediaPosition
   *    MediaType
   *    MediaWeight
   *    MediaClass
   *    TraySwitch
   *    ManualFeed
   *    InsertSheet
   *    Duplex
   *    Collate
   *    ProcessColorModel
   *  etc.
   */
  if (!(broken_flags & (USE_PS_ADOBE_2_0|NO_PAPERSIZE))) { 
    out.begin_comment("BeginFeature:")
       .comment_arg("*PageSize")
       .comment_arg(media_name())
       .end_comment();
    int w = media_width();
    int h = media_height();
    if (w > 0 && h > 0)
      // warning to user is done elsewhere
      fprintf(out.get_file(),
	      "<< /PageSize [ %d %d ] /ImagingBBox null >> setpagedevice\n",
	      w, h);
    out.simple_comment("EndFeature");
  }
}

void ps_printer::begin_page(int n)
{
  out.begin_comment("Page:")
     .comment_arg(i_to_a(n));
  out.comment_arg(i_to_a(++pages_output))
     .end_comment();
  output_style.f = 0;
  output_space_code = 32;
  output_draw_point_size = -1;
  output_line_thickness = -1;
  output_hpos = output_vpos = -1;
  ndefined_styles = 0;
  out.simple_comment("BeginPageSetup");

#if 0
  /*
   *  NOTE:
   *  may decide to do this once per page
   */
  media_set();
#endif

  out.put_symbol("BP")
     .simple_comment("EndPageSetup");
  if (sbuf_color != default_color)
    set_color(&sbuf_color);
}

void ps_printer::end_page(int)
{
  flush_sbuf();
  set_color(&default_color);
  out.put_symbol("EP");
  if (invis_count != 0) {
    error("missing `endinvis' command");
    invis_count = 0;
  }
}

font *ps_printer::make_font(const char *nm)
{
  return ps_font::load_ps_font(nm);
}

ps_printer::~ps_printer()
{
  out.simple_comment("Trailer")
     .put_symbol("end")
     .simple_comment("EOF");
  if (fseek(tempfp, 0L, 0) < 0)
    fatal("fseek on temporary file failed");
  fputs("%!PS-Adobe-", stdout);
  fputs((broken_flags & USE_PS_ADOBE_2_0) ? "2.0" : "3.0", stdout);
  putchar('\n');
  out.set_file(stdout);
  if (cmyk_flag)
    out.begin_comment("Extensions:")
       .comment_arg("CMYK")
       .end_comment();
  out.begin_comment("Creator:")
     .comment_arg("groff")
     .comment_arg("version")
     .comment_arg(Version_string)
     .end_comment();
  {
    fputs("%%CreationDate: ", out.get_file());
#ifdef LONG_FOR_TIME_T
    long
#else
    time_t
#endif
    t = time(0);
    fputs(ctime(&t), out.get_file());
  }
  for (font_pointer_list *f = font_list; f; f = f->next) {
    ps_font *psf = (ps_font *)(f->p);
    rm.need_font(psf->get_internal_name());
  }
  rm.print_header_comments(out);
  out.begin_comment("Pages:")
     .comment_arg(i_to_a(pages_output))
     .end_comment();
  out.begin_comment("PageOrder:")
     .comment_arg("Ascend")
     .end_comment();
  if (!(broken_flags & NO_PAPERSIZE)) {
    int w = media_width();
    int h = media_height();
    if (w > 0 && h > 0)
      fprintf(out.get_file(),
	      "%%%%DocumentMedia: %s %d %d %d %s %s\n",
	      media_name(),			// tag name of media
	      w,				// media width
	      h,				// media height
	      0,				// weight in g/m2
	      "()",				// paper color, e.g. white
	      "()"				// preprinted form type
	     );
    else {
      if (h <= 0)
	// see ps_printer::ps_printer
	warning("bad paper height, defaulting to 11i");
      if (w <= 0)
	warning("bad paper width");
    }
  }
  out.begin_comment("Orientation:")
     .comment_arg(landscape_flag ? "Landscape" : "Portrait")
     .end_comment(); 
  if (ncopies != 1) {
    out.end_line();
    fprintf(out.get_file(), "%%%%Requirements: numcopies(%d)\n", ncopies);
  }
  out.simple_comment("EndComments");
  if (!(broken_flags & NO_PAPERSIZE)) {
    /* gv works fine without this one, but it really should be there. */
    out.simple_comment("BeginDefaults");
    fprintf(out.get_file(), "%%%%PageMedia: %s\n", media_name());
    out.simple_comment("EndDefaults");
  }
  out.simple_comment("BeginProlog");
  rm.output_prolog(out);
  if (!(broken_flags & NO_SETUP_SECTION)) {
    out.simple_comment("EndProlog");
    out.simple_comment("BeginSetup");
  }
#if 1
  /*
   * Define paper (i.e., media) size for entire document here.
   * This allows ps2pdf to correctly determine page size, for instance.
   */
  media_set();
#endif
  rm.document_setup(out);
  out.put_symbol(dict_name)
     .put_symbol("begin");
  if (ndefs > 0)
    ndefs += DEFS_DICT_SPARE;
  out.put_literal_symbol(defs_dict_name)
     .put_number(ndefs + 1)
     .put_symbol("dict")
     .put_symbol("def");
  out.put_symbol(defs_dict_name)
     .put_symbol("begin");
  out.put_literal_symbol("u")
     .put_delimiter('{')
     .put_fix_number(1)
     .put_symbol("mul")
     .put_delimiter('}')
     .put_symbol("bind")
     .put_symbol("def");
  defs += '\0';
  out.special(defs.contents());
  out.put_symbol("end");
  if (ncopies != 1)
    out.put_literal_symbol("#copies")
       .put_number(ncopies)
       .put_symbol("def");
  out.put_literal_symbol("RES")
     .put_number(res)
     .put_symbol("def");
  out.put_literal_symbol("PL");
  if (guess_flag)
    out.put_symbol("PLG");
  else
    out.put_fix_number(paper_length);
  out.put_symbol("def");
  out.put_literal_symbol("LS")
     .put_symbol(landscape_flag ? "true" : "false")
     .put_symbol("def");
  if (manual_feed_flag) {
    out.begin_comment("BeginFeature:")
       .comment_arg("*ManualFeed")
       .comment_arg("True")
       .end_comment()
       .put_symbol("MANUAL")
       .simple_comment("EndFeature");
  }
  encode_fonts();
  while (subencodings) {
    subencoding *tem = subencodings;
    subencodings = subencodings->next;
    encode_subfont(tem);
    out.put_literal_symbol(tem->subfont)
       .put_symbol(make_subencoding_name(tem->idx))
       .put_literal_symbol(tem->p->get_internal_name())
       .put_symbol("RE");
    delete tem;
  }
  out.simple_comment((broken_flags & NO_SETUP_SECTION)
		     ? "EndProlog"
		     : "EndSetup");
  out.end_line();
  out.copy_file(tempfp);
  fclose(tempfp);
}

void ps_printer::special(char *arg, const environment *env, char type)
{
  if (type != 'p')
    return;
  typedef void (ps_printer::*SPECIAL_PROCP)(char *, const environment *);
  static struct {
    const char *name;
    SPECIAL_PROCP proc;
  } proc_table[] = {
    { "exec", &ps_printer::do_exec },
    { "def", &ps_printer::do_def },
    { "mdef", &ps_printer::do_mdef },
    { "import", &ps_printer::do_import },
    { "file", &ps_printer::do_file },
    { "invis", &ps_printer::do_invis },
    { "endinvis", &ps_printer::do_endinvis },
  };
  char *p;
  for (p = arg; *p == ' ' || *p == '\n'; p++)
    ;
  char *tag = p;
  for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
    ;
  if (*p == '\0' || strncmp(tag, "ps", p - tag) != 0) {
    error("X command without `ps:' tag ignored");
    return;
  }
  p++;
  for (; *p == ' ' || *p == '\n'; p++)
    ;
  char *command = p;
  for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
    ;
  if (*command == '\0') {
    error("empty X command ignored");
    return;
  }
  for (unsigned int i = 0; i < sizeof(proc_table)/sizeof(proc_table[0]); i++)
    if (strncmp(command, proc_table[i].name, p - command) == 0) {
      (this->*(proc_table[i].proc))(p, env);
      return;
    }
  error("X command `%1' not recognised", command);
}

// A conforming PostScript document must not have lines longer
// than 255 characters (excluding line termination characters).

static int check_line_lengths(const char *p)
{
  for (;;) {
    const char *end = strchr(p, '\n');
    if (end == 0)
      end = strchr(p, '\0');
    if (end - p > 255)
      return 0;
    if (*end == '\0')
      break;
    p = end + 1;
  }
  return 1;
}

void ps_printer::do_exec(char *arg, const environment *env)
{
  flush_sbuf();
  while (csspace(*arg))
    arg++;
  if (*arg == '\0') {
    error("missing argument to X exec command");
    return;
  }
  if (!check_line_lengths(arg))
    warning("lines in X exec command should"
	    " not be more than 255 characters long");
  out.put_fix_number(env->hpos)
     .put_fix_number(env->vpos)
     .put_symbol("EBEGIN")
     .special(arg)
     .put_symbol("EEND");
  output_hpos = output_vpos = -1;
  output_style.f = 0;
  output_draw_point_size = -1;
  output_line_thickness = -1;
  ndefined_styles = 0;
  if (!ndefs)
    ndefs = 1;
}

void ps_printer::do_file(char *arg, const environment *env)
{
  flush_sbuf();
  while (csspace(*arg))
    arg++;
  if (*arg == '\0') {
    error("missing argument to X file command");
    return;
  }
  const char *filename = arg;
  do {
    ++arg;
  } while (*arg != '\0' && *arg != ' ' && *arg != '\n');
  out.put_fix_number(env->hpos)
     .put_fix_number(env->vpos)
     .put_symbol("EBEGIN");
  rm.import_file(filename, out);
  out.put_symbol("EEND");
  output_hpos = output_vpos = -1;
  output_style.f = 0;
  output_draw_point_size = -1;
  output_line_thickness = -1;
  ndefined_styles = 0;
  if (!ndefs)
    ndefs = 1;
}

void ps_printer::do_def(char *arg, const environment *)
{
  flush_sbuf();
  while (csspace(*arg))
    arg++;
  if (!check_line_lengths(arg))
    warning("lines in X def command should"
	    " not be more than 255 characters long");
  defs += arg;
  if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
    defs += '\n';
  ndefs++;
}

// Like def, but the first argument says how many definitions it contains.

void ps_printer::do_mdef(char *arg, const environment *)
{
  flush_sbuf();
  char *p;
  int n = (int)strtol(arg, &p, 10);
  if (n == 0 && p == arg) {
    error("first argument to X mdef must be an integer");
    return;
  }
  if (n < 0) {
    error("out of range argument `%1' to X mdef command", int(n));
    return;
  }
  arg = p;
  while (csspace(*arg))
    arg++;
  if (!check_line_lengths(arg))
    warning("lines in X mdef command should"
	    " not be more than 255 characters long");
  defs += arg;
  if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
    defs += '\n';
  ndefs += n;
}

void ps_printer::do_import(char *arg, const environment *env)
{
  flush_sbuf();
  while (*arg == ' ' || *arg == '\n')
    arg++;
  char *p;
  for (p = arg; *p != '\0' && *p != ' ' && *p != '\n'; p++)
    ;
  if (*p != '\0')
    *p++ = '\0';
  int parms[6];
  int nparms = 0;
  while (nparms < 6) {
    char *end;
    long n = strtol(p, &end, 10);
    if (n == 0 && end == p)
      break;
    parms[nparms++] = int(n);
    p = end;
  }
  if (csalpha(*p) && (p[1] == '\0' || p[1] == ' ' || p[1] == '\n')) {
    error("scaling indicators not allowed in arguments for X import command");
    return;
  }
  while (*p == ' ' || *p == '\n')
    p++;
  if (nparms < 5) {
    if (*p == '\0')
      error("too few arguments for X import command");
    else
      error("invalid argument `%1' for X import command", p);
    return;
  }
  if (*p != '\0') {
    error("superfluous argument `%1' for X import command", p);
    return;
  }
  int llx = parms[0];
  int lly = parms[1];
  int urx = parms[2];
  int ury = parms[3];
  int desired_width = parms[4];
  int desired_height = parms[5];
  if (desired_width <= 0) {
    error("bad width argument `%1' for X import command: must be > 0",
	  desired_width);
    return;
  }
  if (nparms == 6 && desired_height <= 0) {
    error("bad height argument `%1' for X import command: must be > 0",
	  desired_height);
    return;
  }
  if (llx == urx) {
    error("llx and urx arguments for X import command must not be equal");
    return;
  }
  if (lly == ury) {
    error("lly and ury arguments for X import command must not be equal");
    return;
  }
  if (nparms == 5) {
    int old_wid = urx - llx;
    int old_ht = ury - lly;
    if (old_wid < 0)
      old_wid = -old_wid;
    if (old_ht < 0)
      old_ht = -old_ht;
    desired_height = int(desired_width*(double(old_ht)/double(old_wid)) + .5);
  }
  if (env->vpos - desired_height < 0)
    warning("top of imported graphic is above the top of the page");
  out.put_number(llx)
     .put_number(lly)
     .put_fix_number(desired_width)
     .put_number(urx - llx)
     .put_fix_number(-desired_height)
     .put_number(ury - lly)
     .put_fix_number(env->hpos)
     .put_fix_number(env->vpos)
     .put_symbol("PBEGIN");
  rm.import_file(arg, out);
  // do this here just in case application defines PEND
  out.put_symbol("end")
     .put_symbol("PEND");
}

void ps_printer::do_invis(char *, const environment *)
{
  invis_count++;
}

void ps_printer::do_endinvis(char *, const environment *)
{
  if (invis_count == 0)
    error("unbalanced `endinvis' command");
  else
    --invis_count;
}

printer *make_printer()
{
  return new ps_printer(user_paper_length);
}

static void usage(FILE *stream);

int main(int argc, char **argv)
{
  setlocale(LC_NUMERIC, "C");
  program_name = argv[0];
  string env;
  static char stderr_buf[BUFSIZ];
  setbuf(stderr, stderr_buf);
  int c;
  static const struct option long_options[] = {
    { "help", no_argument, 0, CHAR_MAX + 1 },
    { "version", no_argument, 0, 'v' },
    { NULL, 0, 0, 0 }
  };
  while ((c = getopt_long(argc, argv, "b:c:F:gI:lmp:P:vw:", long_options, NULL))
	 != EOF)
    switch(c) {
    case 'b':
      // XXX check this
      broken_flags = atoi(optarg);
      bflag = 1;
      break;
    case 'c':
      if (sscanf(optarg, "%d", &ncopies) != 1 || ncopies <= 0) {
	error("bad number of copies `%s'", optarg);
	ncopies = 1;
      }
      break;
    case 'F':
      font::command_line_font_dir(optarg);
      break;
    case 'g':
      guess_flag = 1;
      break;
    case 'I':
      include_search_path.command_line_dir(optarg);
      break;
    case 'l':
      landscape_flag = 1;
      break;
    case 'm':
      manual_feed_flag = 1;
      break;
    case 'p':
      if (!font::scan_papersize(optarg, 0,
				&user_paper_length, &user_paper_width))
	error("invalid custom paper size `%1' ignored", optarg);
      break;
    case 'P':
      env = "GROPS_PROLOGUE";
      env += '=';
      env += optarg;
      env += '\0';
      if (putenv(strsave(env.contents())))
	fatal("putenv failed");
      break;
    case 'v':
      printf("GNU grops (groff) version %s\n", Version_string);
      exit(0);
      break;
    case 'w':
      if (sscanf(optarg, "%d", &linewidth) != 1 || linewidth < 0) {
	error("bad linewidth `%1'", optarg);
	linewidth = -1;
      }
      break;
    case CHAR_MAX + 1: // --help
      usage(stdout);
      exit(0);
      break;
    case '?':
      usage(stderr);
      exit(1);
      break;
    default:
      assert(0);
    }
  font::set_unknown_desc_command_handler(handle_unknown_desc_command);
  SET_BINARY(fileno(stdout));
  if (optind >= argc)
    do_file("-");
  else {
    for (int i = optind; i < argc; i++)
      do_file(argv[i]);
  }
  return 0;
}

static void usage(FILE *stream)
{
  fprintf(stream,
"usage: %s [-glmv] [-b n] [-c n] [-w n] [-I dir] [-P prologue]\n"
"       [-F dir] [files ...]\n",
    program_name);
}
