386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Tue, 30 Apr 1991 23:00:45 +0000 (15:00 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Tue, 30 Apr 1991 23:00:45 +0000 (15:00 -0800)
Work on file usr/src/usr.bin/groff/grops/Makefile.gnu
Work on file usr/src/usr.bin/groff/grops/TODO
Work on file usr/src/usr.bin/groff/grops/grops.1
Work on file usr/src/usr.bin/groff/grops/ps.cc

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/usr.bin/groff/grops/Makefile.gnu [new file with mode: 0644]
usr/src/usr.bin/groff/grops/TODO [new file with mode: 0644]
usr/src/usr.bin/groff/grops/grops.1 [new file with mode: 0644]
usr/src/usr.bin/groff/grops/ps.cc [new file with mode: 0644]

diff --git a/usr/src/usr.bin/groff/grops/Makefile.gnu b/usr/src/usr.bin/groff/grops/Makefile.gnu
new file mode 100644 (file)
index 0000000..b6db2fa
--- /dev/null
@@ -0,0 +1,93 @@
+#Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
+#     Written by James Clark (jjc@jclark.uucp)
+#
+#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 1, 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 groff; see the file LICENSE.  If not, write to the Free Software
+#Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# define PAGE to be letter if your PostScript printer uses 8.5x11 paper (USA)
+# and define it to be A4, if it uses A4 paper (rest of the world)
+PAGE=A4
+#PAGE=letter
+BINDIR=/usr/local/bin
+CC=g++
+CFLAGS=-g #-DBROKEN_SPOOLER
+OLDCC=gcc
+OLDCFLAGS=-g
+MLIBS=-lm
+INCLUDES=-I../driver -I../lib
+DEFINES=
+LDFLAGS=-g
+OBJECTS=ps.o
+SOURCES=ps.c
+MISC=Makefile devgps
+BINDIR=/usr/local/bin
+FONTDIR=/usr/local/lib/groff/font
+MACRODIR=/usr/local/lib/groff/tmac
+ETAGS=etags
+ETAGSFLAGS=-p
+
+.c.o:
+       $(CC) -c $(INCLUDES) $(CFLAGS) $(DEFINES) $<
+
+all: grops psbb devps
+
+grops: $(OBJECTS) ../driver/libdriver.a ../lib/libgroff.a
+       $(CC) $(LDFLAGS) -o $@ $(OBJECTS) \
+       ../driver/libdriver.a ../lib/libgroff.a $(MLIBS)
+
+ps.o: ../driver/printer.h ../driver/driver.h ../lib/font.h \
+      ../lib/stringclass.h ../lib/cset.h
+
+psbb: psbb.o
+       $(OLDCC) $(LDFLAGS) -o $@ psbb.o
+
+psbb.o: psbb.c
+       $(OLDCC) $(OLDCFLAGS) -c psbb.c
+
+install.bin: grops psbb
+       -[ -d $(BINDIR) ] || mkdir $(BINDIR)
+       cp grops psbb $(BINDIR)
+       @cd devps; \
+       $(MAKE) \
+       "FONTDIR=$(FONTDIR)" "PAGE=$(PAGE)" "BINDIR=$(BINDIR)" install.bin
+
+install.nobin:
+       -[ -d $(MACRODIR) ] || mkdir $(MACRODIR)
+       cp tmac.ps $(MACRODIR)
+       @echo Making install.nobin in devps
+       @cd devps; \
+       $(MAKE) \
+       "FONTDIR=$(FONTDIR)" "PAGE=$(PAGE)" "BINDIR=$(BINDIR)" install.nobin
+
+install: install.bin install.nobin
+
+
+TAGS : $(SOURCES)
+       $(ETAGS) $(ETAGSFLAGS) $(SOURCES)
+
+clean:
+       -rm -f *.o psbb core grops
+
+distclean: clean
+       -rm -f TAGS
+
+realclean: distclean
+
+devps: FORCE
+       @echo Making all in devps
+       @cd devps; $(MAKE) "FONTDIR=$(FONTDIR)" "PAGE=$(PAGE)" all
+
+FORCE:
diff --git a/usr/src/usr.bin/groff/grops/TODO b/usr/src/usr.bin/groff/grops/TODO
new file mode 100644 (file)
index 0000000..e4f0682
--- /dev/null
@@ -0,0 +1,14 @@
+For efficiency it might be better to have the printer interface have
+support for the t and u commands.
+
+Angles in arc command: don't generate more digits after the decimal
+point than are necessary.
+
+Possibly generate BoundingBox comment.
+
+Per font composite character mechanism (sufficient for fractions).
+
+Consider whether we ought to do rounding of graphical objects other
+than lines. What's the point?
+
+Error messages should refer to output page number.
diff --git a/usr/src/usr.bin/groff/grops/grops.1 b/usr/src/usr.bin/groff/grops/grops.1
new file mode 100644 (file)
index 0000000..a321593
--- /dev/null
@@ -0,0 +1,635 @@
+.\" -*- nroff -*-
+.TH GROPS @MAN1EXT@ "17 September 1990" "Groff Version @VERSION@"
+.SH NAME
+grops \- PostScript driver for groff
+.SH SYNOPSIS
+.B grops
+[
+.B \-v
+] [
+.B \-l
+] [
+.BI \-F dir
+] [
+.BI \-w n
+] [
+.BI \-c n
+] [
+.IR files \|.\|.\|.
+]
+.SH DESCRIPTION
+.B grops
+translates the output of
+.B gtroff
+to PostScript.
+Normally
+.B grops
+should invoked by using the groff command
+with a
+.B \-Tps
+option.
+.if '@DEVICE@'ps' (Actually, this is the default for groff.)
+If no files are given,
+.B grops
+will read the standard input.
+A filename of
+.B \-
+will also cause
+.B grops
+to read the standard input.
+PostScript output is written to the standard output.
+.SH OPTIONS
+.TP
+.BI \-c n
+Print
+.I n
+copies of each page.
+.TP
+.BI \-l
+Print the document in landscape format.
+.TP
+.BI \-F dir
+Search the directory
+.IB dir /dev name
+for font and device description files;
+.I name
+is the name of the device, usually
+.BR ps .
+.TP
+.BI \-w n
+Lines should be drawn using a thickness of
+.I n
+thousandths of an em.
+.TP
+.B \-v
+Print the version number.
+.SH USAGE
+There are styles called
+.BR R ,
+.BR I ,
+.BR B ,
+and
+.B BI
+mounted at font positions 1 to 4.
+The fonts are grouped into families
+.BR A ,
+.BR BM ,
+.BR C ,
+.BR H ,
+.BR HN ,
+.BR N ,
+.B P
+and
+.B T
+having members in each of these styles:
+.de FT
+.if '\\*(.T'ps' .ft \\$1
+..
+.TP
+.B AR
+.FT AR
+AvantGarde-Book
+.FT
+.TP
+.B AI
+.FT AI
+AvantGarde-BookOblique
+.FT
+.TP
+.B AB
+.FT AB
+AvantGarde-Demi
+.FT
+.TP
+.B ABI
+.FT ABI
+AvantGarde-DemiOblique
+.FT
+.TP
+.B BMR
+.FT BMR
+Bookman-Light
+.FT
+.TP
+.B BMI
+.FT BMI
+Bookman-LightItalic
+.FT
+.TP
+.B BMB
+.FT BMB
+Bookman-Demi
+.FT
+.TP
+.B BMBI
+.FT BMBI
+Bookman-DemiItalic
+.FT
+.TP
+.B CR
+.FT CR
+Courier
+.FT
+.TP
+.B CI
+.FT CI
+Courier-Oblique
+.FT
+.TP
+.B CB
+.FT CB
+Courier-Bold
+.FT
+.TP
+.B CBI
+.FT CBI
+Courier-BoldOblique
+.FT
+.TP
+.B HR
+.FT HR
+Helvetica
+.FT
+.TP
+.B HI
+.FT HI
+Helvetica-Oblique
+.FT
+.TP
+.B HB
+.FT HB
+Helvetica-Bold
+.FT
+.TP
+.B HBI
+.FT HBI
+Helvetica-BoldOblique
+.FT
+.TP
+.B HNR
+.FT HNR
+Helvetica-Narrow
+.FT
+.TP
+.B HNI
+.FT HNI
+Helvetica-Narrow-Oblique
+.FT
+.TP
+.B HNB
+.FT HNB
+Helvetica-Narrow-Bold
+.FT
+.TP
+.B HNBI
+.FT HNBI
+Helvetica-Narrow-BoldOblique
+.FT
+.TP
+.B NR
+.FT NR
+NewCenturySchlbk-Roman
+.FT
+.TP
+.B NI
+.FT NI
+NewCenturySchlbk-Italic
+.FT
+.TP
+.B NB
+.FT NB
+NewCenturySchlbk-Bold
+.FT
+.TP
+.B NBI
+.FT NBI
+NewCenturySchlbk-BoldItalic
+.FT
+.TP
+.B PR
+.FT PR
+Palatino-Roman
+.FT
+.TP
+.B PI
+.FT PI
+Palatino-Italic
+.FT
+.TP
+.B PB
+.FT PB
+Palatino-Bold
+.FT
+.TP
+.B PBI
+.FT PBI
+Palatino-BoldItalic
+.FT
+.TP
+.B TR
+.FT TR
+Times-Roman
+.FT
+.TP
+.B TI
+.FT TI
+Times-Italic
+.FT
+.TP
+.B TB
+.FT TB
+Times-Bold
+.FT
+.TP
+.B TBI
+.FT TBI
+Times-BoldItalic
+.FT
+.LP
+There is also the following font which is not a member of a family:
+.TP
+.B ZCMI
+.FT ZCMI
+ZapfChancery-MediumItalic
+.FT
+.LP
+There are also some special fonts called
+.B SS
+and
+.BR S .
+Zapf Dingbats is avilable as
+.BR ZD
+and a reversed version of ZapfDingbats (with symbols pointing in the opposite
+direction) is available as
+.BR ZDR ;
+most characters in these fonts are unnamed and must be accessed using
+.BR \eN .
+.LP
+.B grops
+understands various X commands produced using the
+.B \eX
+escape sequence;
+.B grops
+will only interpret commands that begin with a
+.B ps:
+tag.
+.TP
+.BI \eX'ps:\ exec\  code '
+This executes the arbitrary PostScript commands in
+.IR code .
+The PostScript currentpoint will be set to the position of the
+.B \eX
+command before executing
+.IR code .
+The origin will be at the top left corner of the page,
+and y coordinates will increase down the page.
+A procedure
+.B u
+will be defined that converts groff units
+to the coordinate system in effect.
+For example, 
+.RS
+.IP
+.B
+\&.nr x 1i
+.br
+.B
+\eX'ps: exec \enx u 0 rlineto stroke'
+.br
+.RE
+.IP
+will draw a horizontal line one inch long.
+.I code
+may make changes to the graphics state,
+but any changes will persist only to the
+end of the page.
+Any definitions will also persist only until the end of the page.
+If you use the
+.B \eY
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+For example,
+.RS
+.IP
+.nf
+.ft B
+\&.nr x 1i
+\&.de y
+\&ps: exec
+\&\enx u 0 rlineto
+\&stroke
+\&..
+\&\eYy
+.fi
+.ft R
+.LP
+is another way to draw a horizontal line one inch long.
+.RE
+.TP
+.BI \eX'ps:\ file\  name '
+This is the same as the
+.B exec
+command except that the PostScript code is read from file
+.IR name .
+.TP
+.BI \eX'ps:\ def\  code '
+Place a PostScript definition contained in
+.I code
+in the prologue.
+There should be at most one definition per
+.B \eX
+command.
+Long definitions can be split over several
+.B \eX
+commands;
+all the
+.I code
+arguments are simply joined together separated by newlines.
+The definitions are placed in a dictionary which is automatically
+pushed on the dictionary stack when an
+.B exec
+command is executed.
+If you use the
+.B \eY
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.TP
+.BI \eX'ps:\ mdef\  n\ code  '
+Like
+.BR def ,
+except that
+.I code
+may contain up to
+.I n
+definitions.
+.B grops
+needs to know how many definitions
+.I code
+contains
+so that it can create an apppropriately sized PostScript dictionary
+to contain them.
+.TP
+.BI \eX'ps:\ import\  file\ llx\ lly\ urx\ ury\ width\ \fR[\fP\ height\ \fR]\fP '
+Import a PostScript graphic from
+.IR file .
+The arguments
+.IR llx ,
+.IR lly ,
+.IR urx ,
+and
+.I ury
+give the bounding box of the graphic in the default PostScript
+coordinate system; they should all be integers;
+.I llx
+and
+.I lly
+are the x and y coordinates of the lower left
+corner of the graphic;
+.I urx
+and
+.I ury
+are the x and y coordinates of the upper right corner of the graphic;
+.I width
+and
+.I height
+are integers that give the desired width and height in groff
+units of the graphic.
+The graphic will be scaled so that it has this width and height
+and translated so that the lower left corner of the graphic is
+located at the position associated with
+.B \eX
+command.
+If the height argument is omitted it will be scaled uniformly in the
+x and y directions so that it has the specified width.
+Note that the contents of the
+.B \eX
+command are not interpreted by
+.BR gtroff ;
+so vertical space for the graphic is not automatically added,
+and the
+.I width
+and
+.I height
+arguments are not allowed to have attached scaling indicators.
+If the PostScript file complies with the Adobe Document Structuring
+Conventions and contains a
+.B %%BoundingBox
+comment, then the bounding box can be automatically
+extracted from within groff by using the
+.B sy
+request to run the
+.B psbb
+command.
+.RS
+.LP
+The
+.B \-mps
+macros (which are automatically loaded when
+.B grops
+is run by the groff command) include a
+.B PSPIC
+macro which allows a picture to be easily imported.
+This has the format
+.IP
+.BI .PSPIC\  file\  \fR[ width\  \fR[ height \fR]]
+.LP
+.I file
+is the name of the file containing the illustration;
+.I width
+and
+.I height
+give the desired width and height of the graphic.
+The
+.I width
+and
+.I height
+arguments may have scaling indicators attached;
+the default scaling indicator is
+.BR i .
+This macro will scale the graphic uniformly
+in the x and y directions so that it is no more than
+.I width
+wide
+and
+.I height
+high.
+.RE
+.LP
+The input to
+.B grops
+must be in the format output by
+.BR gtroff (@MAN1EXT@).
+This is described in
+.BR groff_out (@MAN1EXT@).
+In addition the device and font description files for the device used
+must meet certain requirements.
+The device and font description files supplied for
+.B ps
+device meet all these requirements.
+.BR afmtodit (@MAN1EXT@)
+can be used to create font files from AFM files.
+The resolution must be an integer multiple of 72 times the
+.BR sizescale .
+The
+.B ps
+device uses a resolution of 72000 and a sizescale of 1000.
+The device description file should contain a command
+.IP
+.BI paperlength\  n
+.LP
+which says that output should be generated which is suitable for
+printing on a page whose length is
+.I n
+machine units.
+Each font description file must contain a command
+.IP
+.BI internalname\  psname
+.LP
+which says that the PostScript name of the font is
+.IR psname .
+It may also contain a command
+.IP
+.BI encoding\  enc_file
+.LP
+which says that
+the PostScript font should be reencoded using the encoding described in
+.IR enc_file ;
+this file should consist of a sequence of lines of the form:
+.IP
+.I
+pschar code
+.LP
+where
+.I pschar
+is the PostScript name of the character,
+and
+.I code
+is its position in the encoding expressed as a decimal integer.
+The code for each character given in the font file must correspond
+to the code for the character in encoding file, or to the code in the default
+encoding for the font if the PostScript font is not to be reencoded.
+This code can be used with the
+.B \eN
+escape sequence in
+.B gtroff
+to select the character,
+even if the character does not have a groff name.
+Every character in the font file must exist in the PostScript font, and 
+the widths given in the font file must match the widths used
+in the PostScript font.
+.B grops
+will assume that a character with a groff name of
+.B space
+is blank (makes no marks on the page);
+it can make use of such a character to generate more efficient and
+compact PostScript output.
+.LP
+.B grops
+can automatically include the downloadable fonts necessary
+to print the document.
+Any downloadable fonts which should, when required, be included by
+.B grops
+must be listed in the file
+.BR @FONTDIR@/devps/download ;
+this should consist of lines of the form
+.IP
+.I
+font  filename
+.LP
+where
+.I font
+is the PostScript name of the font,
+and
+.I filename
+is the name of the file containing the font;
+lines beginning with
+.B #
+and blank lines are ignored;
+fields may be separated by tabs or spaces;
+.I filename
+will be searched for using the same mechanism that is used
+for groff font metric files.
+The
+.B download
+file itself will also be searched for using this mechanism.
+.LP
+If a file imported with the
+.B
+\eX import
+command complies with the Adobe Document Structuring Conventions,
+then
+.B grops
+will include any fonts that are needed by the imported file
+and are listed in the
+.B download
+file.
+.LP
+.B grops
+is also able to handle inter-font dependencies.
+Any downloadable font that depends on another font must indicate
+this by following the Structuring Conventions and listing the fonts
+on which it depends in the
+.B %%DocumentFonts
+comment.
+For example, suppose that you have a downloadable font called Garamond,
+and also a downloadable font called Garamond-Outline
+which depends on Garamond
+(typically it would be defined to copy Garamond's font dictionary,
+and change the PaintType),
+then the downloadable font file for Garamond-Outline should start like this
+.IP
+.B
+%!PS-Adobe-2.1
+.br
+.B
+%%DocumentFonts: Garamond
+.LP
+.B grops
+will then ensure that whenever Garamond-Outline is included, Garamond is
+included before it.
+In this case both Garamond and Garamond-Outline would need to be listed
+in the
+.B download
+file.
+A downloadable font should list every font that it depends on
+even if that font is resident in the printer (like, for example, Symbol);
+this will enable
+.B grops
+to generate a correct
+.B %%DocumentFonts
+comment for its output;
+the font should not include its own name in the
+.B %%DocumentFonts
+comment.
+.SH FILES
+.TP \w'\fB@FONTDIR@/devps/download'u+2n
+.B @FONTDIR@/devps/DESC
+Device desciption file.
+.TP
+.BI @FONTDIR@/devps/ F
+Font description file for font
+.IR F .
+.TP
+.B @FONTDIR@/devps/download
+List of downloadable fonts.
+.TP
+.B @FONTDIR@/devps/text.enc
+Encoding used for text fonts.
+.TP
+.B @MACRODIR@/tmac.ps
+Macros for use with
+.BR grops .
+.TP
+.BI /tmp/grops XXXXXX
+Temporary file.
+.SH "SEE ALSO"
+.BR afmtodit (@MAN1EXT@),
+.BR groff (@MAN1EXT@),
+.BR gtroff (@MAN1EXT@),
+.BR psbb (@MAN1EXT@),
+.BR groff_out (@MAN5EXT@),
+.BR groff_font (@MAN5EXT@)
+.br
+.I "Groff Character Names"
diff --git a/usr/src/usr.bin/groff/grops/ps.cc b/usr/src/usr.bin/groff/grops/ps.cc
new file mode 100644 (file)
index 0000000..1d2f408
--- /dev/null
@@ -0,0 +1,1972 @@
+// -*- C++ -*-
+/* Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
+     Written by James Clark (jjc@jclark.uucp)
+
+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 1, 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 groff; see the file LICENSE.  If not, write to the Free Software
+Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+extern "C" {
+  // Sun's stdlib.h fails to declare this.
+  char *mktemp(char *);
+}
+
+static int landscape_flag = 0;
+static int ncopies = 1;
+static int linewidth = -1;
+
+#define DEFAULT_LINEWIDTH 40   /* in ems/1000 */
+#define FILL_MAX 1000
+
+// Maximum number of definitions in the prologue (used for sizing the
+// dictionary.)
+
+const int MAX_PROLOGUE_DEFS = 50;
+const char *const dict_name = "grops";
+const char *const defs_dict_name = "DEFS";
+const int DEFS_DICT_SPARE = 50;
+
+#define PROLOGUE "prologue"
+
+double degrees(double r)
+{
+  return r*180.0/M_PI;
+}
+
+double radians(double d)
+{
+  return d*M_PI/180.0;
+}
+
+inline double transform_fill(int fill)
+{
+  return 1 - fill/double(FILL_MAX);
+}
+
+class ps_output {
+public:
+  ps_output(FILE *, int max_line_length);
+  ps_output &put_string(const char *, int);
+  ps_output &put_number(int);
+  ps_output &put_fix_number(int);
+  ps_output &put_float(double);
+  ps_output &put_symbol(const char *);
+  ps_output &put_literal_symbol(const char *);
+  ps_output &set_fixed_point(int);
+  ps_output &simple_comment(const char *);
+  ps_output &begin_comment(const char *);
+  ps_output &comment_arg(const char *);
+  ps_output &end_comment();
+  ps_output &set_file(FILE *);
+  ps_output &include_file(FILE *);
+  ps_output &copy_file(FILE *);
+  ps_output &end_line();
+  ps_output &put_delimiter(char);
+  ps_output &special(const char *);
+private:
+  FILE *fp;
+  int col;
+  int max_line_length;         // not including newline
+  int need_space;
+  int fixed_point;
+};
+
+ps_output::ps_output(FILE *f, int n)
+: fp(f), max_line_length(n), col(0), need_space(0), fixed_point(0)
+{
+}
+
+ps_output &ps_output::set_file(FILE *f)
+{
+  fp = f;
+  col = 0;
+  return *this;
+}
+
+ps_output &ps_output::include_file(FILE *infp)
+{
+  if (col != 0)
+    putc('\n', fp);
+  int c;
+#ifdef BROKEN_SPOOLER
+  // Strip the first line if it's a comment.  I believe
+  // some spoolers get confused by %! in the middle of a file.
+  if ((c = getc(infp)) == '%') {
+    while ((c = getc(infp)) != '\n' && c != '\r' && c != EOF)
+      ;
+  }
+  else if (c != EOF)
+    ungetc(c, infp);
+#endif /* BROKEN_SPOOLER */
+  // We strip out lines beginning with STRIPENDUM.
+#ifdef BROKEN_SPOOLER
+#define STRIPENDUM "%%"
+#else
+#define STRIPENDUM "%%IncludeFont:"
+#endif
+  // Index into STRIPENDUM of next character to be matched.
+  int match = 0;
+  while ((c = getc(infp)) != EOF) {
+    if (match >= 0) {
+      if (c == STRIPENDUM[match]) {
+       if (++match == sizeof(STRIPENDUM) - 1) {
+         match = -1;
+         while ((c = getc(infp)) != EOF)
+           if (c == '\r' || c == '\n') {
+             match = 0;
+             break;
+           }
+       }
+      }
+      else {
+       for (int i = 0; i < match; i++)
+         putc(STRIPENDUM[i], fp);
+       putc(c, fp);
+       match = (c == '\n' || c == '\r' ? 0 : -1);
+      }
+    }
+    else {
+      putc(c, fp);
+      match = (c == '\n' || c == '\r' ? 0 : -1);
+    }
+  }
+  for (int i = 0; i < match; i++)
+    putc(STRIPENDUM[i], fp);
+  if (match != 0)
+    putc('\n', fp);
+  int lastc = '\n';
+  while ((c = getc(infp)) != EOF) {
+    putc(c, fp);
+    lastc = c;
+  }
+  if (lastc != '\n')
+    putc('\n', fp);
+  col = 0;
+  need_space = 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;
+  }
+  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;
+  for (int i = 0; i < n; i++) {
+    char c = s[i];
+    if (isascii(c) && isprint(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 (isascii(c) && isprint(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 = iftoa(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 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_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_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(int argc, const char **argv);
+  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(0), reencoded_name(0), encoding_index(-1)
+{
+}
+
+ps_font::~ps_font()
+{
+  delete encoding;
+  delete reencoded_name;
+}
+
+void ps_font::handle_unknown_font_command(int argc, const char **argv)
+{
+  if (strcmp(argv[0], "encoding") == 0) {
+    if (argc != 2)
+      error("`encoding' command requires exactly 1 argument");
+    else
+      encoding = strsave(argv[1]);
+  }
+}
+
+
+struct style {
+  font *f;
+  int point_size;
+  int height;
+  int slant;
+  style();
+  style(font *, int, int, int);
+  int operator==(const style &) const;
+  int operator!=(const style &) const;
+};
+
+style::style() : f(0)
+{
+}
+
+style::style(font *p, int sz, int h, int sl)
+: f(p), point_size(sz), height(h), slant(sl)
+{
+}
+
+int style::operator==(const style &s) const
+{
+  return (f == s.f && point_size == s.point_size
+         && height == s.height && slant == s.slant);
+}
+
+int style::operator!=(const style &s) const
+{
+  return !(*this == s);
+}
+
+struct depend_list;
+
+struct document_font {
+  enum { LISTED = 01, NEEDED = 02, SUPPLIED = 04 };
+  char *name;
+  char *filename;
+  int flags;
+  document_font *next;
+  depend_list *depends_on;
+  int mark;
+  document_font(const char *);
+  ~document_font();
+  void download(ps_output &);
+};
+
+struct depend_list {
+  document_font *p;
+  depend_list *next;
+};
+
+
+class ps_printer : public printer {
+  FILE *tempfp;
+  ps_output out;
+  int res;
+  int space_char_index;
+  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;
+  style output_style;
+  int output_hpos;
+  int output_vpos;
+  int output_draw_point_size;
+  int line_thickness;
+  int output_line_thickness;
+  int fill;
+  unsigned char output_space_code;
+  enum { MAX_DEFINED_STYLES = 50 };
+  style defined_styles[MAX_DEFINED_STYLES];
+  int ndefined_styles;
+  int next_encoding_index;
+  string defs;
+  int ndefs;
+  document_font *doc_fonts;
+
+  void flush_sbuf();
+  void set_style(const style &);
+  void set_space_code(unsigned char c);
+  int set_encoding_index(ps_font *);
+  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 set_line_thickness(const environment *);
+  void fill_path();
+  void merge_download_fonts();
+  void merge_import_fonts(FILE *);
+  void merge_ps_fonts();
+  void print_font_comment();
+  void print_supplied_font_comment();
+  void print_needed_font_comment();
+  void print_include_font_comments();
+  document_font *lookup_doc_font(const char *);
+  void encode_fonts();
+  void download_fonts();
+  void define_encoding(const char *, int);
+  void reencode_font(ps_font *);
+  void read_download_file();
+public:
+  ps_printer();
+  ~ps_printer();
+  void set_char(int i, font *f, const environment *env, int w);
+  void draw(int code, int *p, int np, const environment *env);
+  void begin_page(int);
+  void end_page();
+  void special(char *arg, const environment *env);
+  font *make_font(const char *);
+  void end_of_line();
+};
+
+ps_printer::ps_printer()
+: pages_output(0),
+  sbuf_len(0),
+  output_hpos(-1),
+  output_vpos(-1),
+  out(0, 79),
+  ndefined_styles(0),
+  next_encoding_index(0),
+  line_thickness(-1),
+  fill(FILL_MAX + 1),
+  ndefs(0),
+  doc_fonts(0)
+{
+  static char temp_filename[] = "/tmp/gropsXXXXXX";
+  mktemp(temp_filename);
+  tempfp = fopen(temp_filename, "w+");
+  if (tempfp == 0)
+    fatal("can't open temporary file `%1': %2",
+         temp_filename, strerror(errno));
+  if (unlink(temp_filename) < 0)
+    error("can't unlink `%1': %2", temp_filename, strerror(errno));
+  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_char_index = font::name_to_index("space");
+  paper_length = font::paperlength;
+  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++;
+}
+
+void ps_printer::set_char(int i, font *f, const environment *env, int w)
+{
+  if (i == space_char_index)
+    return;
+  unsigned char code = f->get_code(i);
+  style sty(f, 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) {
+      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_char_index)) {
+           sbuf_space_code = f->get_code(space_char_index);
+           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;
+}
+
+int is_small_h(int n)
+{
+  return n < (font::res*2)/72 && n > -(font::res*10)/72;
+}
+
+int is_small_v(int n)
+{
+  return n < (font::res*4)/72 && n > -(font::res*4)/72;
+}
+
+static char *make_encoding_name(int encoding_index)
+{
+  static char buf[3 + INT_DIGITS + 1];
+  sprintf(buf, "ENC%d", encoding_index);
+  return buf;
+}
+
+const char *const WS = " \t\n\r";
+
+void ps_printer::define_encoding(const char *encoding, int encoding_index)
+{
+  char *vec[256];
+  for (int 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;
+  char buf[256];
+  while (fgets(buf, 512, fp) != 0) {
+    char *p = buf;
+    while (isascii(*p) && isspace(*p))
+      p++;
+    if (*p != '#' && *p != '\0' && (p = strtok(buf, WS)) != 0) {
+      char *q = strtok(0, WS);
+      int n;
+      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++;
+  }
+  delete path;
+  out.put_literal_symbol(make_encoding_name(encoding_index));
+  out.put_delimiter('[');
+  for (i = 0; i < 256; i++) {
+    if (vec[i] == 0)
+      out.put_literal_symbol(".notdef");
+    else
+      out.put_literal_symbol(vec[i]);
+  }
+  out.put_delimiter(']').put_symbol("def");
+}
+
+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);
+    }
+  }
+  delete done_encoding;
+}
+
+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 (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;
+  }
+  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_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
+      || !is_small_h(output_hpos - sbuf_start_hpos)
+      || !is_small_v(output_vpos - sbuf_vpos))
+    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_char_index, 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 sym[2];
+  sym[0] = 'A' + 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(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;
+    }
+  }
+}
+
+void ps_printer::fill_path()
+{
+  if (fill > FILL_MAX)
+    out.put_symbol("BL");
+  else
+    out.put_float(transform_fill(fill)).put_symbol("FL");
+}
+
+void ps_printer::draw(int code, int *p, int np, const environment *env)
+{
+  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();
+    }
+    else {
+      set_line_thickness(env);
+      out.put_symbol("ST");
+    }
+    break;
+  case 'l':
+    if (np != 2) {
+      error("2 arguments required for line");
+      break;
+    }
+    set_line_thickness(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();
+    }
+    else {
+      set_line_thickness(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();
+      }
+      else {
+       set_line_thickness(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(env);
+      out.put_symbol("ST");
+    }
+    break;
+  case 'a':
+    {
+      if (np != 4) {
+       error("4 arguments required for arc");
+       break;
+      }
+      set_line_thickness(env);
+      int x = p[0] + p[2];
+      int y = p[1] + p[3];
+      double n = p[0]*double(x) + p[1]*double(y);
+      if (n == 0)
+       out.put_fix_number(x + env->hpos)
+          .put_fix_number(y + env->vpos)
+          .put_fix_number(env->hpos)
+          .put_fix_number(env->vpos)
+          .put_symbol("DL");
+      else {
+       double k = (double(x)*x + double(y)*y)/(2.0*n);
+       double cx = k*p[0];
+       double cy = k*p[1];
+       out.put_fix_number(env->hpos + int(cx))
+          .put_fix_number(env->vpos + int(cy))
+          .put_fix_number(int(sqrt(cx*cx + cy*cy)))
+          .put_float(degrees(atan2(-cy, -cx)))
+          .put_float(degrees(atan2(y - cy, x - cx)))
+          .put_symbol("DA");
+      }
+    }
+    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;
+    }
+  case 'f':
+    {
+      if (np != 1 && np != 2) {
+       error("1 argument required for fill");
+       break;
+      }
+      fill = p[0];
+      if (fill < 0 || fill > FILL_MAX) {
+       // This means fill with the current color.
+       fill = FILL_MAX + 1;
+      }
+      break;
+    }      
+  default:
+    error("unrecognised drawing command `%1'", char(code));
+    break;
+  }
+
+  output_hpos = output_vpos = -1;
+}
+
+void ps_printer::begin_page(int n)
+{
+  out.begin_comment("Page:").comment_arg(itoa(n));
+  out.comment_arg(itoa(++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.put_symbol("BP");
+}
+
+void ps_printer::end_page()
+{
+  flush_sbuf();
+  out.put_symbol("EP");
+}
+
+font *ps_printer::make_font(const char *nm)
+{
+  return ps_font::load_ps_font(nm);
+}
+
+ps_printer::~ps_printer()
+{
+  out.simple_comment("Trailer");
+  out.put_symbol("end");
+  out.end_line();
+  if (fseek(tempfp, 0L, 0) < 0)
+    fatal("fseek on temporary file failed");
+  fputs("%!PS-Adobe-2.1\n", stdout);
+  out.set_file(stdout);
+  {
+    extern const char *version_string;
+    out.begin_comment("Creator:")
+       .comment_arg("groff")
+       .comment_arg("version")
+       .comment_arg(version_string)
+       .end_comment();
+  }
+  read_download_file();
+  merge_ps_fonts();
+  merge_download_fonts();
+  print_font_comment();
+  print_supplied_font_comment();
+  print_needed_font_comment();
+  out.begin_comment("Pages:").comment_arg(itoa(pages_output)).end_comment();
+  out.simple_comment("EndComments");
+  char *path;
+  FILE *fp = font::open_file(PROLOGUE, &path);
+  if (fp == 0)
+    fatal("can't find `%1'", PROLOGUE);
+  out.put_literal_symbol(dict_name);
+  out.put_number(MAX_DEFINED_STYLES + MAX_PROLOGUE_DEFS).put_symbol("dict");
+  out.put_symbol("def");
+  out.put_symbol(dict_name).put_symbol("begin");
+  out.include_file(fp);
+  fclose(fp);
+  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");
+  delete path;
+  out.put_symbol("end");
+#ifndef BROKEN_SPOOLER
+  out.simple_comment("EndProlog");
+#endif /* !BROKEN_SPOOLER */
+  print_include_font_comments();
+  download_fonts();
+#ifndef BROKEN_SPOOLER
+  out.simple_comment("BeginSetup");
+#endif /* !BROKEN_SPOOLER */
+  out.put_symbol(dict_name).put_symbol("begin");
+  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").put_fix_number(paper_length).put_symbol("def");
+  out.put_literal_symbol("LS")
+     .put_symbol(landscape_flag ? "true" : "false")
+     .put_symbol("def");
+  encode_fonts();
+#ifndef BROKEN_SPOOLER
+  out.simple_comment("EndSetup");
+#else /* !BROKEN_SPOOLER */
+  out.simple_comment("EndProlog");
+#endif /* BROKEN_SPOOLER */
+  out.end_line();
+  out.copy_file(tempfp);
+  fclose(tempfp);
+}
+
+void ps_printer::special(char *arg, const environment *env)
+{
+  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,
+  };
+  for (char *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("X command without `ps:' tag ignored");
+    return;
+  }
+  for (int i = 0; i < sizeof(proc_table)/sizeof(proc_table[0]); i++)
+    if (strncmp(command, proc_table[i].name, p - command) == 0) {
+      flush_sbuf();
+      (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)
+{
+  while (csspace(*arg))
+    arg++;
+  if (*arg == '\0') {
+    error("missing argument to X exec command");
+    return;
+  }
+  if (!check_line_lengths(arg)) {
+    error("lines in X exec command must not be more than 255 characters long");
+    return;
+  }
+  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;
+}
+
+void ps_printer::do_file(char *arg, const environment *env)
+{
+  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');
+  FILE *fp = fopen(filename, "r");
+  if (!fp) {
+    error("can't open `%1': %2", filename, strerror(errno));
+    return;
+  }
+  out.put_fix_number(env->hpos)
+     .put_fix_number(env->vpos)
+     .put_symbol("EBEGIN")
+     .include_file(fp)
+     .put_symbol("EEND");
+  fclose(fp);
+  output_hpos = output_vpos = -1;
+  output_style.f = 0;
+  output_draw_point_size = -1;
+  output_line_thickness = -1;
+  ndefined_styles = 0;
+}
+
+void ps_printer::do_def(char *arg, const environment *)
+{
+  while (csspace(*arg))
+    arg++;
+  if (!check_line_lengths(arg)) {
+    error("lines in X def command must not be more than 255 characters long");
+    return;
+  }
+  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 *)
+{
+  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)) {
+    error("lines in X mdef command must not be more than 255 characters long");
+    return;
+  }
+  defs += arg;
+  if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
+    defs += '\n';
+  ndefs += n;
+}
+
+void ps_printer::do_import(char *arg, const environment *env)
+{
+  while (*arg == ' ' || *arg == '\n')
+    arg++;
+  for (char *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("superflous 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");
+  FILE *fp = fopen(arg, "r");
+  if (fp == 0) {
+    error("can't open `%1': %2", arg, strerror(errno));
+    return;
+  }
+  merge_import_fonts(fp);
+  out.put_literal_symbol("level1")
+     .put_symbol("save")
+     .put_symbol("def");
+  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("PICTURE");
+  // Put our dictionary off the dictionary stack so that it isn't
+  // zapped by unfriendly applications.
+  out.put_symbol("end");
+  // Disable showpage.
+  out.put_literal_symbol("showpage")
+     .put_delimiter('{')
+     .put_delimiter('}')
+     .put_symbol("def");
+  out.begin_comment("BeginDocument:")
+     .comment_arg(arg)
+     .end_comment();
+  out.include_file(fp);
+  fclose(fp);
+  out.simple_comment("EndDocument");
+  // Clear junk off operand stack, to lessen chance of an invalidrestore.
+  out.put_symbol("clear");
+  out.put_symbol(dict_name)
+     .put_symbol("begin");
+  out.put_symbol("level1")
+     .put_symbol("restore");
+}
+
+static void add_font(const char *name, int t, document_font **pp)
+{
+  for (; *pp; pp = &(*pp)->next)
+    if (strcmp(name, (*pp)->name) == 0) {
+      (*pp)->flags |= t;
+      return;
+    }
+  *pp = new document_font(name);
+  (*pp)->flags = t;
+}
+
+// Skip the rest of the current line, including any line termination
+// characters.
+
+static int skip_line(FILE *fp)
+{
+  // The spec says any combination of \n and \r is allowed as a line
+  // termination.  It seems wrong to allow multiple \n's or multiple
+  // \r's since this would mean we were swallowing blank lines.
+  for (;;) {
+    int c = getc(fp);
+    if (c == '\n') {
+      c = getc(fp);
+      if (c == EOF)
+       return 0;
+      if (c != '\r')
+       ungetc(c, fp);
+      return 1;
+    }
+    if (c == '\r') {
+      c = getc(fp);
+      if (c == EOF)
+       return 0;
+      if (c != '\n')
+       ungetc(c, fp);
+      return 1;
+    }
+    if (c == EOF)
+      break;
+  }
+  return 0;
+}
+
+static void parse_fonts_arg(FILE *fp, int type, document_font **font_listp,
+                           int *at_endp)
+{
+  // More than enough room for a single name.
+  char buf[256];
+  int c = getc(fp);
+  for (;;) {
+    while (c == ' ' || c == '\t')
+      c = getc(fp);
+    if (c == EOF || c == '\n' || c == '\r')
+      break;
+    int i = 0;
+    do {
+      if (i >= sizeof(buf) - 1)
+       return;
+      if (c == '\0')
+       return;
+      buf[i++] = c;
+      c = getc(fp);
+    } while (c != '\n' && c != '\r' && c != ' ' && c != '\t' && c != EOF);
+    buf[i++] = '\0';
+    if (strcmp(buf, "(atend)") == 0 && at_endp != 0)
+      *at_endp |= type;
+    else
+      add_font(buf, type, font_listp);
+  }
+  if (c == '\n' || c == '\r')
+    ungetc(c, fp);
+}
+
+static document_font *read_document_fonts(FILE *fp)
+{
+  int last_comment = 0;
+  int document_fonts_at_end = 0;
+  // This says which comments we've seen in the header.  The first of each
+  // is the significant one.
+  int header_comments = 0;
+  char buf[sizeof("DocumentSuppliedFonts:")];
+#define MAGIC "%!PS-Adobe-"
+  if (fread(buf, 1, sizeof(MAGIC)-1, fp) != sizeof(MAGIC)-1
+      || memcmp(buf, MAGIC, sizeof(MAGIC)-1) != 0)
+    return 0;
+  document_font *font_list = 0;
+  enum { HEADER, BODY, TRAILER } state = HEADER;
+  int level = 0;
+  while (skip_line(fp)) {
+    if (state == BODY && document_fonts_at_end == 0)
+      break;
+    int c = getc(fp);
+    if (c == '%')
+      c = getc(fp);
+    if (c == EOF)
+      break;
+    if (c != '%') {
+      if (state == HEADER)
+       state = BODY;
+      if (c == '\n' || c == '\r')
+       ungetc(c, fp);
+      continue;
+    }
+    c = getc(fp);
+    if (c == EOF)
+      break;
+    if (c == '+') {
+      if (last_comment)
+       parse_fonts_arg(fp, last_comment, &font_list, 0);
+      continue;
+    }
+    last_comment = 0;
+    int i = 0;
+    while (c != '\r' && c != '\n' && c != ' ' && c != '\t' && c != EOF) {
+      if (i >= sizeof(buf) - 1) {
+       i = 0;
+       break;
+      }
+      buf[i++] = c;
+      if (c == ':')
+       break;
+      c = getc(fp);
+    }
+    if (c == '\r' || c == '\n')
+      ungetc(c, fp);
+    buf[i++] = '\0';
+    if (strcmp(buf, "BeginDocument:") == 0)
+      level++;
+    else if (strcmp(buf, "EndDocument") == 0) {
+      if (level > 0)
+       level--;
+    }
+    else if (level == 0) {
+      if (strcmp(buf, "Trailer") == 0)
+       state = TRAILER;
+      else if (strcmp(buf, "EndComments") == 0) {
+       if (state == HEADER)
+         state = BODY;
+      }
+      else if (state == HEADER) {
+       int comment_type = 0;
+       if (strcmp(buf, "DocumentFonts:") == 0)
+         comment_type = document_font::LISTED;
+       else if (strcmp(buf, "DocumentNeededFonts:") == 0)
+         comment_type = document_font::NEEDED;
+       else if (strcmp(buf, "DocumentSuppliedFonts:") == 0)
+         comment_type = document_font::SUPPLIED;
+       if (comment_type != 0 && !(header_comments & comment_type)) {
+         parse_fonts_arg(fp, comment_type, &font_list,
+                         (comment_type == document_font::LISTED
+                          ? &document_fonts_at_end
+                          : 0));
+         last_comment = comment_type;
+         header_comments |= comment_type;
+       }
+      }
+      else if (state == TRAILER && strcmp(buf, "DocumentFonts:") == 0) {
+       parse_fonts_arg(fp, document_font::LISTED, &font_list, 0);
+       last_comment = document_font::LISTED;
+      }
+    }
+  }
+  return font_list;
+}
+
+document_font *ps_printer::lookup_doc_font(const char *nm)
+{
+  for (document_font **p = &doc_fonts; *p; p = &(*p)->next)
+    if (strcmp((*p)->name, nm) == 0)
+      return *p;
+  return *p = new document_font(nm);
+}
+
+void ps_printer::merge_download_fonts()
+{
+  for (document_font *p = doc_fonts; p; p = p->next)
+    if (p->filename && (p->flags & document_font::NEEDED)) {
+      char *path = 0;
+      FILE *fp = font::open_file(p->filename, &path);
+      delete path;
+      if (fp) {
+       document_font *depends = read_document_fonts(fp);
+       fclose(fp);
+       while (depends) {
+         document_font *tem = depends->next;
+         if (strcmp(p->name, depends->name) != 0) {
+           if ((depends->flags & document_font::LISTED)
+               && !(depends->flags & document_font::SUPPLIED))
+             depends->flags |= document_font::NEEDED;
+           // We ignore the LISTED bit from now on.
+           document_font *q = lookup_doc_font(depends->name);
+           if (q->filename && (depends->flags & document_font::NEEDED)) {
+             depend_list *dep = new depend_list;
+             dep->next = p->depends_on;
+             dep->p = q;
+             p->depends_on = dep;
+             if (!(q->flags & document_font::NEEDED)) {
+               // Move q to the end of the list.
+               for (document_font **pp = &doc_fonts;
+                    *pp != q;
+                    pp = &(*pp)->next)
+                 ;
+               *pp = q->next;
+               q->next = 0;
+               for (; *pp; pp = &(*pp)->next)
+                 ;
+               *pp = q;
+             }
+           }
+           q->flags |= depends->flags;
+         }
+         delete depends;
+         depends = tem;
+       }
+      }
+      else {
+       error("can't find font file `%1': %2", p->filename, strerror(errno));
+       delete p->filename;
+       p->filename = 0;
+      }
+    }
+}
+
+void ps_printer::merge_import_fonts(FILE *fp)
+{
+  document_font *p = read_document_fonts(fp);
+  rewind(fp);
+  while (p) {
+    if ((p->flags & document_font::LISTED)
+       && !(p->flags & document_font::SUPPLIED))
+      p->flags |= document_font::NEEDED;
+    document_font *tem = p->next;
+    document_font *q = lookup_doc_font(p->name);
+    q->flags |= p->flags;
+    delete p;
+    p = tem;
+  }
+}
+
+void ps_printer::merge_ps_fonts()
+{
+  for (font_pointer_list *f = font_list; f; f = f->next) {
+    ps_font *psf = (ps_font *)(f->p);
+    document_font *p = lookup_doc_font(psf->get_internal_name());
+    p->flags |= document_font::NEEDED;
+  }
+}
+
+void ps_printer::print_font_comment()
+{
+  out.begin_comment("DocumentFonts:");
+  for (document_font *list = doc_fonts; list; list = list->next)
+    if (list->flags)
+      out.comment_arg(list->name);
+  out.end_comment();
+}
+
+void ps_printer::print_supplied_font_comment()
+{
+  out.begin_comment("DocumentSuppliedFonts:");
+  for (document_font *list = doc_fonts; list; list = list->next)
+    if (((list->flags & document_font::NEEDED) && list->filename)
+       || ((list->flags & document_font::SUPPLIED)
+#if 0
+           && !(list->flags & document_font::NEEDED)
+#endif
+       ))
+      out.comment_arg(list->name);
+  out.end_comment();
+}
+
+void ps_printer::print_needed_font_comment()
+{
+  out.begin_comment("DocumentNeededFonts:");
+  for (document_font *list = doc_fonts; list; list = list->next)
+    if ((list->flags & document_font::NEEDED) && !list->filename)
+      out.comment_arg(list->name);
+  out.end_comment();
+}
+
+void ps_printer::print_include_font_comments()
+{
+  for (document_font *list = doc_fonts; list; list = list->next)
+    if ((list->flags & document_font::NEEDED) && !list->filename)
+      out.begin_comment("IncludeFont:").comment_arg(list->name).end_comment();
+}
+  
+void ps_printer::download_fonts()
+{
+  for (document_font *p = doc_fonts; p; p = p->next)
+    p->download(out);
+}
+
+void ps_printer::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_doc_font(p)->filename = strsave(q);
+  }
+  delete path;
+  fclose(fp);
+}
+
+document_font::document_font(const char *s)
+: name(strsave(s)), filename(0), flags(0), next(0), depends_on(0), mark(0)
+{
+  assert(name != 0);
+}
+
+document_font::~document_font()
+{
+  delete name;
+  delete filename;
+  while (depends_on) {
+    depend_list *tem = depends_on->next;
+    delete depends_on;
+    depends_on = tem;
+  }
+}
+
+void document_font::download(ps_output &out)
+{
+  if (!filename)
+    return;
+  if (!(flags & NEEDED))
+      return;
+  // Do a reverse topological sort on the dependency graph.
+  if (mark == 0) {
+    mark = -1;
+    for (depend_list *p = depends_on; p; p = p->next)
+      p->p->download(out);
+    char *path = 0;
+    FILE *fp = font::open_file(filename, &path);
+    // This shouldn't normally happen because we checked that the file
+    // exists in merge_download_fonts.
+    if (!fp)
+      fatal("can't open `%1': %2", filename, strerror(errno));
+    out.begin_comment("BeginFont:").comment_arg(name).end_comment();
+    out.begin_comment("BeginDocument:").comment_arg(filename).end_comment();
+    out.include_file(fp);
+    out.simple_comment("EndDocument");
+    out.simple_comment("EndFont");
+    fclose(fp);
+    delete path;
+    mark = 1;
+  }
+  else if (mark < 0)
+    error("loop detected in font dependencies for `%1'", name);
+}
+
+printer *make_printer()
+{
+  return new ps_printer;
+}
+
+static void usage();
+
+int main(int argc, char **argv)
+{
+  program_name = argv[0];
+  static char stderr_buf[BUFSIZ];
+  setbuf(stderr, stderr_buf);
+  int c;
+  while ((c = getopt(argc, argv, "F:lc:w:v")) != EOF)
+    switch(c) {
+    case 'v':
+      {
+       extern const char *version_string;
+       fprintf(stderr, "grops version %s\n", version_string);
+       fflush(stderr);
+       break;
+      }
+    case 'c':
+      if (sscanf(optarg, "%d", &ncopies) != 1 || ncopies <= 0) {
+       error("bad number of copies `%s'", optarg);
+       ncopies = 1;
+      }
+      break;
+    case 'l':
+      landscape_flag = 1;
+      break;
+    case 'F':
+      font::command_line_font_dir(optarg);
+      break;
+    case 'w':
+      if (sscanf(optarg, "%d", &linewidth) != 1 || linewidth < 0) {
+       error("bad linewidth `%s'", optarg);
+       linewidth = -1;
+      }
+      break;
+    case '?':
+      usage();
+      break;
+    default:
+      assert(0);
+    }
+  if (optind >= argc)
+    do_file("-");
+  else {
+    for (int i = optind; i < argc; i++)
+      do_file(argv[i]);
+  }
+  delete pr;
+  exit(0);
+}
+
+static void usage()
+{
+  fprintf(stderr, "usage: %s [-l] [-c n] [-w n] [-F dir] [-v] [files ...]\n",
+         program_name);
+  exit(1);
+}