summaryrefslogtreecommitdiffstats
path: root/src/devices/grops
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/grops')
-rw-r--r--src/devices/grops/TODO24
-rw-r--r--src/devices/grops/grops.1.man1831
-rw-r--r--src/devices/grops/grops.am38
-rw-r--r--src/devices/grops/ps.cpp1894
-rw-r--r--src/devices/grops/ps.h129
-rw-r--r--src/devices/grops/psfig.diff106
-rw-r--r--src/devices/grops/psrm.cpp1189
7 files changed, 5211 insertions, 0 deletions
diff --git a/src/devices/grops/TODO b/src/devices/grops/TODO
new file mode 100644
index 0000000..eab8f83
--- /dev/null
+++ b/src/devices/grops/TODO
@@ -0,0 +1,24 @@
+Read PFB files directly.
+
+Generate %%For comment.
+
+Generate %%Title comment.
+
+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.
+
+Search for downloadable fonts using their PostScript names if not
+found in download file.
+
+Separate path for searching for downloadable font files.
+
+Clip to the BoundingBox when importing documents.
diff --git a/src/devices/grops/grops.1.man b/src/devices/grops/grops.1.man
new file mode 100644
index 0000000..d0ec21d
--- /dev/null
+++ b/src/devices/grops/grops.1.man
@@ -0,0 +1,1831 @@
+.TH grops @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grops \-
+.I groff
+output driver for PostScript
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2018, 2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grops_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'ps' .ft \\$1
+. if '\\*(.T'pdf' .ft \\$1
+..
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grops
+.RB [ \-glm ]
+.RB [ \-b\~\c
+.IR brokenness-flags ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-P\~\c
+.IR prologue-file ]
+.RB [ \-w\~\c
+.IR rule-thickness ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grops
+.B \-\-help
+.YS
+.
+.
+.SY grops
+.B \-v
+.
+.SY grops
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+PostScript output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into PostScript.
+.
+Normally,
+.I grops
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~ps \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grops .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grotty
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+When called with multiple
+.I file
+arguments,
+.I grops
+doesn't produce a valid document structure
+(one conforming to the Document Structuring Conventions).
+.
+To print such concatenated output,
+it is necessary to deactivate DSC handling in the printing program or
+previewer.
+.
+.
+.P
+See section \[lq]Font installation\[rq] below for a guide to installing
+fonts for
+.IR grops .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-b\~ n
+Work around problems with spoolers,
+previewers,
+and older printers.
+.
+Normally,
+.I grops
+produces output at PostScript \%LanguageLevel\~2 that conforms to
+version 3.0 of the Document Structuring Conventions.
+.
+Some software and devices can't handle such a data stream.
+.
+The value
+.RI of\~ n
+determines what
+.I grops
+does to make its output acceptable to such consumers.
+.
+If
+.I n
+is
+.BR 0 ,
+.I grops
+employs no workarounds,
+which is the default;
+it can be changed by modifying the
+.B broken
+directive in
+.IR grops 's
+.I DESC
+file.
+.
+.
+.IP
+Add\~1 to suppress generation of
+.B %%Begin\%Document\%Setup
+and
+.B %%End\%Document\%Setup
+comments;
+this is needed for early versions of TranScript that get confused by
+anything between the
+.B %%End\%Prolog
+comment and the first
+.B %%Page
+comment.
+.
+.
+.IP
+Add\~2 to omit lines in included files beginning with
+.BR %!\& ,
+which confuse Sun's
+.I pageview
+previewer.
+.
+.
+.IP
+Add\~4 to omit lines in included files beginning with
+.BR %%Page ,
+.B %%Trailer
+and
+.BR %%End\%Prolog ;
+this is needed for spoolers that don't understand
+.B %%Begin\%Document
+and
+.B %%End\%Document
+comments.
+.
+.
+.IP
+Add\~8 to write
+.B %!PS\-Adobe\-2.0
+rather than
+.B %!PS\-Adobe\-3.0
+as the first line of the PostScript output;
+this is needed when using Sun's Newsprint with a printer that requires
+page reversal.
+.
+.
+.IP
+Add\~16 to omit media size information
+(that is,
+output neither a
+.B %%Document\%Media
+comment nor the
+.B setpagedevice
+PostScript command).
+.
+This was the behavior of
+.I groff
+1.18.1 and earlier;
+it is
+needed for older printers that don't understand PostScript
+\%LanguageLevel\~2,
+and is also necessary if the output is further processed to produce an
+EPS file;
+see subsection \[lq]Escapsulated PostScript\[rq] below.
+.
+.
+.TP
+.BI \-c\~ n
+Output
+.I n
+copies of each page.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for
+font and device description and PostScript prologue files;
+.I name
+is the name of the device,
+usually
+.BR ps .
+.
+.
+.TP
+.B \-g
+Generate PostScript code to guess the page length.
+.
+The guess is correct only if the imageable area is vertically centered
+on the page.
+.
+This option allows you to generate documents that can be printed on both
+U.S.\& letter and A4 paper formats without change.
+.
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files named in
+.B \[rs]X\[aq]ps: file\[aq]
+and
+.B \[rs]X\[aq]ps: import\[aq]
+escape sequences.
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.
+.TP
+.B \-l
+Use landscape orientation rather than portrait.
+.
+.
+.TP
+.B \-m
+Turn on manual feed for the document.
+.
+.
+.TP
+.BI \-p\~ fmt
+Set physical dimensions of output medium,
+overriding the
+.BR \%papersize ,
+.BR \%paperlength ,
+and
+.B \%paperwidth
+directives in the
+.I DESC
+file.
+.
+.I fmt
+can be any argument accepted by the
+.B \%papersize
+directive;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI \-P\~ prologue
+Use the file
+.IR prologue ,
+sought in the
+.I groff
+font search path,
+as the PostScript prologue,
+overriding the default
+(see section \[lq]Files\[rq] below)
+and the environment variable
+.I GROPS_PROLOGUE.
+.
+.
+.TP
+.BI \-w\~ n
+Draw rules (lines) with a thickness of
+.IR n \~thousandths
+of an em.
+.
+The default thickness is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The input to
+.I grops
+must be in the format output by
+.MR @g@troff @MAN1EXT@ ,
+described in
+.MR groff_out @MAN5EXT@ .
+.
+In addition,
+the device and font description files for the device used must meet
+certain requirements.
+.
+The device resolution must be an integer multiple of\~72 times the
+.BR sizescale .
+.
+The device description file must contain a valid paper format;
+see
+.MR groff_font @MAN5EXT@ .
+.
+Each font description file must contain a directive
+.
+.RS
+.EX
+.RI internalname\~ psname
+.EE
+.RE
+.
+which says that the PostScript name of the font is
+.IR psname .
+.
+.
+.P
+A font description file may also contain a directive
+.
+.RS
+.EX
+.RI encoding\~ enc-file
+.EE
+.RE
+.
+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
+.
+.
+.RS
+.EX
+.I pschar code
+.EE
+.RE
+.
+where
+.I pschar
+is the PostScript name of the character,
+and
+.I code
+is its position in the encoding expressed as a decimal integer;
+valid values are in the range 0 to\~255.
+.
+Lines starting with
+.B #
+and blank lines are ignored.
+.
+The code for each character given in the font description 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 \[rs]N
+escape sequence in
+.I @g@troff
+to select the character,
+even if it does not have a
+.I groff
+glyph name.
+.
+Every character in the font description file must exist in the
+PostScript font,
+and the widths given in the font description file must match the widths
+used in the PostScript font.
+.
+.I grops
+assumes that a character with a
+.I 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.
+.
+.
+.P
+.I grops
+is able to display all glyphs in a PostScript font;
+it is not limited to 256 of them.
+.
+.I enc-file
+(or the default encoding if no encoding file is specified)
+just defines the
+order of glyphs for the first 256 characters;
+all other glyphs are accessed with additional encoding vectors which
+.I grops
+produces on the fly.
+.
+.
+.P
+.I grops
+can embed fonts in a document that are necessary to render it;
+this is called \[lq]downloading\[rq].
+.
+Such fonts must be in PFA format.
+.
+Use
+.MR pfbtops @MAN1EXT@
+to convert a Type\~1 font in PFB format.
+.
+Downloadable fonts must be listed a
+.I download
+file containing lines of the form
+.
+.RS
+.EX
+.I psname file
+.EE
+.RE
+.
+where
+.I psname
+is the PostScript name of the font,
+and
+.I file
+is the name of the file containing it;
+lines beginning with
+.B #
+and blank lines are ignored;
+fields may be separated by tabs or spaces.
+.
+.I file
+is sought using the same mechanism as that for
+.I groff
+font description files.
+.
+The
+.I download
+file itself is also sought using this mechanism;
+currently,
+only the first matching file found in the device and font description
+search path is used.
+.
+.
+.P
+If the file containing a downloadable font or imported document
+conforms to the Adobe Document Structuring Conventions,
+then
+.I grops
+interprets any comments in the files sufficiently to ensure that its
+own output is conforming.
+.
+It also supplies any needed font resources that are listed in the
+.I download
+file
+as well as any needed file resources.
+.
+It is also able to handle inter-resource dependencies.
+.
+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 it is necessary for Garamond to appear before Garamond-Outline in
+the PostScript document.
+.
+.I grops
+handles this automatically provided that the downloadable font file
+for Garamond-Outline indicates its dependence on Garamond by means of
+the Document Structuring Conventions,
+for example by beginning with the following lines.
+.
+.RS
+.EX
+%!PS\-Adobe\-3.0 Resource\-Font
+%%DocumentNeededResources: font Garamond
+%%EndComments
+%%IncludeResource: font Garamond
+.EE
+.RE
+.
+In this case,
+both Garamond and Garamond-Outline would need to be listed
+in the
+.I download
+file.
+.
+A downloadable font should not include its own name in a
+.B %%Document\%Supplied\%Resources
+comment.
+.
+.
+.P
+.I grops
+does not interpret
+.B %%Document\%Fonts
+comments.
+.
+The
+.BR %%Document\%Needed\%Resources ,
+.BR %%Document\%Supplied\%Resources ,
+.BR %%Include\%Resource ,
+.BR %%Begin\%Resource ,
+and
+.B %%End\%Resource
+comments
+(or possibly the old
+.BR %%Document\%Needed\%Fonts ,
+.BR %%Document\%Supplied\%Fonts ,
+.BR %%Include\%Font ,
+.BR %%Begin\%Font ,
+and
+.B %%End\%Font
+comments)
+should be used.
+.
+.
+.P
+The default stroke and fill color is black.
+.
+For colors defined in the \[lq]rgb\[rq] color space,
+.B setrgbcolor
+is used;
+for \[lq]cmy\[rq] and \[lq]cmyk\[rq],
+.BR setcmykcolor ;
+and for \[lq]gray\[rq],
+.BR setgray .
+.
+.B setcmykcolor
+is a PostScript \%LanguageLevel\~2 command and thus not available on
+some older printers.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.P
+Styles called
+.BR R ,
+.BR I ,
+.BR B ,
+and
+.B BI
+mounted at font positions 1 to\~4.
+.
+Text fonts are grouped into families
+.BR A ,
+.BR BM ,
+.BR C ,
+.BR H ,
+.BR HN ,
+.BR N ,
+.BR P ,
+.RB and\~ T ,
+each having members in each of these styles.
+.
+.
+.RS
+.TP
+.B AR
+.FT AR
+AvantGarde-Book
+.FT
+.
+.TQ
+.B AI
+.FT AI
+AvantGarde-BookOblique
+.FT
+.
+.TQ
+.B AB
+.FT AB
+AvantGarde-Demi
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+AvantGarde-DemiOblique
+.FT
+.
+.TQ
+.B BMR
+.FT BMR
+Bookman-Light
+.FT
+.
+.TQ
+.B BMI
+.FT BMI
+Bookman-LightItalic
+.FT
+.
+.TQ
+.B BMB
+.FT BMB
+Bookman-Demi
+.FT
+.
+.TQ
+.B BMBI
+.FT BMBI
+Bookman-DemiItalic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier-Oblique
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier-Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier-BoldOblique
+.FT
+.
+.TQ
+.B HR
+.FT HR
+Helvetica
+.FT
+.
+.TQ
+.B HI
+.FT HI
+Helvetica-Oblique
+.FT
+.
+.TQ
+.B HB
+.FT HB
+Helvetica-Bold
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+Helvetica-BoldOblique
+.FT
+.
+.TQ
+.B HNR
+.FT HNR
+Helvetica-Narrow
+.FT
+.
+.TQ
+.B HNI
+.FT HNI
+Helvetica-Narrow-Oblique
+.FT
+.
+.TQ
+.B HNB
+.FT HNB
+Helvetica-Narrow-Bold
+.FT
+.
+.TQ
+.B HNBI
+.FT HNBI
+Helvetica-Narrow-BoldOblique
+.FT
+.
+.TQ
+.B NR
+.FT NR
+NewCenturySchlbk-Roman
+.FT
+.
+.TQ
+.B NI
+.FT NI
+NewCenturySchlbk-Italic
+.FT
+.
+.TQ
+.B NB
+.FT NB
+NewCenturySchlbk-Bold
+.FT
+.
+.TQ
+.B NBI
+.FT NBI
+NewCenturySchlbk-BoldItalic
+.FT
+.
+.TQ
+.B PR
+.FT PR
+Palatino-Roman
+.FT
+.
+.TQ
+.B PI
+.FT PI
+Palatino-Italic
+.FT
+.
+.TQ
+.B PB
+.FT PB
+Palatino-Bold
+.FT
+.
+.TQ
+.B PBI
+.FT PBI
+Palatino-BoldItalic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+Times-Roman
+.FT
+.
+.TQ
+.B TI
+.FT TI
+Times-Italic
+.FT
+.
+.TQ
+.B TB
+.FT TB
+Times-Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+Times-BoldItalic
+.FT
+.RE
+.
+.
+.br
+.ne 2v
+.P
+Another text font is not a member of a family.
+.
+.
+.RS
+.TP
+.B ZCMI
+.FT ZCMI
+ZapfChancery-MediumItalic
+.FT
+.RE
+.
+.
+.P
+Special fonts include
+.BR S ,
+the PostScript Symbol font;
+.BR ZD ,
+Zapf Dingbats;
+.B SS
+(slanted symbol),
+which contains oblique forms of lowercase Greek letters derived from
+Symbol;
+.BR EURO ,
+which offers a Euro glyph for use with old devices lacking it;
+and
+.BR ZDR ,
+a reversed version of ZapfDingbats
+(with symbols flipped about the vertical axis).
+.
+Most glyphs in these fonts are unnamed and must be accessed using
+.BR \[rs]N .
+.
+The last three are not standard PostScript fonts,
+but supplied by
+.I groff
+and therefore included in the default
+.I download
+file.
+.
+.
+.\" ====================================================================
+.SS "Device control commands"
+.\" ====================================================================
+.
+.I grops
+recognizes device control commands produced by the
+.B \[rs]X
+escape sequence,
+but interprets only those that begin with a
+.RB \[lq] ps: \[rq]
+tag.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec\~" code \[aq]
+.RS
+Execute the arbitrary PostScript commands
+.IR code .
+.
+The PostScript
+.I \%currentpoint
+is set to the
+.I groff
+drawing position when the
+.B \[rs]X
+escape sequence is interpreted before executing
+.IR code .
+.
+The origin is at the top left corner of the page;
+.IR x \~coordinates
+increase to the right,
+and
+.IR y \~coordinates
+down the page.
+.
+A
+.RB procedure\~ u
+is defined that converts
+.I groff
+basic units to the coordinate system in effect
+(provided the user doesn't change the scale).
+.
+For example,
+.
+.RS
+.EX
+\&.nr x 1i
+\[rs]X\[aq]ps: exec \[rs]nx u 0 rlineto stroke\[aq]
+.EE
+.RE
+.
+draws a horizontal line one inch long.
+.
+.I code
+may make changes to the graphics state,
+but any changes persist only to the end of the page.
+.
+A dictionary containing the definitions specified by the
+.B def
+and
+.B mdef
+commands is on top of the dictionary stack.
+.
+If your code adds definitions to this dictionary,
+you should allocate space for them using
+.RB \[lq] "\[rs]X\[aq]ps: mdef\~"
+.IB n \[aq]\c
+\[rq].
+.
+Any definitions persist only until the end of the page.
+.
+If you use the
+.B \[rs]Y
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.
+For example,
+.
+.RS
+.EX
+\&.nr x 1i
+\&.de y
+\&ps: exec
+\&\[rs]nx u 0 rlineto
+\&stroke
+\&..
+\&\[rs]Yy
+.EE
+.RE
+.
+is another way to draw a horizontal line one inch long.
+.
+The single backslash before
+.RB \[lq] nx \[rq]\[em]the
+only reason to use a register while defining the macro
+.RB \[lq] y \[rq]\[em]is
+to convert a user-specified dimension
+.RB \[lq] 1i \[rq]
+to
+.I groff
+basic units which are in turn converted to PostScript units with the
+.B u
+procedure.
+.
+.
+.P
+.I grops
+wraps user-specified PostScript code into a dictionary,
+nothing more.
+.
+In particular,
+it doesn't start and end the inserted code with
+.B save
+and
+.BR restore ,
+respectively.
+.
+This must be supplied by the user,
+if necessary.
+.RE
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: file\~" name \[aq]
+This is the same as the
+.B exec
+command except that the PostScript code is read from file
+.IR name .
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: def\~" code \[aq]
+Place a PostScript definition contained in
+.I code
+in the prologue.
+.
+There should be at most one definition per
+.B \[rs]X
+command.
+.
+Long definitions can be split over several
+.B \[rs]X
+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 \[rs]Y
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: mdef\~" "n code" \[aq]
+Like
+.BR def ,
+except that
+.I code
+may contain up to
+.IR n \~definitions.
+.
+.I grops
+needs to know how many definitions
+.I code
+contains
+so that it can create an appropriately sized PostScript dictionary
+to contain them.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: import\~" "file llx lly urx ury width\~"\c
+.RI [ height ]\c
+.B \[aq]
+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
+.I x
+and
+.IR y \~coordinates
+of the lower left corner of the graphic;
+.I urx
+and
+.I ury
+are the
+.I x
+and
+.IR 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
+.I groff
+basic units of the graphic.
+.
+.
+.IP
+The graphic is 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 \[rs]X
+command.
+.
+If the height argument is omitted it is scaled uniformly in the
+.I x
+and
+.IR y \~axes
+so that it has the specified width.
+.
+.
+.IP
+The contents of the
+.B \[rs]X
+command are not interpreted by
+.IR @g@troff ,
+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.
+.
+.
+.IP
+If the PostScript file complies with the Adobe Document Structuring
+Conventions and contains a
+.B %%Bounding\%Box
+comment,
+then the bounding box can be automatically extracted from within
+.I groff
+input by using the
+.B psbb
+request.
+.
+.
+.IP
+See
+.MR groff_tmac @MAN5EXT@
+for a description of the
+.B PSPIC
+macro which provides a convenient high-level interface for inclusion of
+PostScript graphics.
+.
+.
+.TP
+.B \[rs]X\[aq]ps: invis\[aq]
+.TQ
+.B \[rs]X\[aq]ps: endinvis\[aq]
+No output is generated for text and drawing commands
+that are bracketed with these
+.B \[rs]X
+commands.
+.
+These commands are intended for use when output from
+.I @g@troff
+is previewed before being processed with
+.IR grops ;
+if the previewer is unable to display certain characters
+or other constructs,
+then other substitute characters or constructs can be used for
+previewing by bracketing them with these
+.B \[rs]X
+commands.
+.
+.
+.RS
+.P
+For example,
+.I \%gxditview
+is not able to display a proper
+.B \[rs][em]
+character because the standard X11 fonts do not provide it;
+this problem can be overcome by executing the following
+request
+.
+.
+.IP
+.EX
+\&.char \[rs][em] \[rs]X\[aq]ps: invis\[aq]\[rs]
+\[rs]Z\[aq]\[rs]v\[aq]-.25m\[aq]\[rs]h\[aq].05m\[aq]\c
+\[rs]D\[aq]l .9m 0\[aq]\[rs]h\[aq].05m\[aq]\[aq]\[rs]
+\[rs]X\[aq]ps: endinvis\[aq]\[rs][em]
+.EE
+.
+.
+.P
+In this case,
+.I \%gxditview
+is unable to display the
+.B \[rs][em]
+character and draws the line,
+whereas
+.I grops
+prints the
+.B \[rs][em]
+character
+and ignores the line
+(this code is already in file
+.IR Xps.tmac ,
+which is loaded if a document intended for
+.I grops
+is previewed with
+.IR \%gxditview ).
+.RE
+.
+.
+.P
+If a PostScript procedure
+.B BPhook
+has been defined via a
+.RB \[lq] "ps: def" \[rq]
+or
+.RB \[lq] "ps: mdef" \[rq]
+device control command,
+it is executed at the beginning of every page
+(before anything is drawn or written by
+.IR groff ).
+.
+For example,
+to underlay the page contents with the word \[lq]DRAFT\[rq] in light
+gray,
+you might use
+.
+.
+.RS
+.P
+.EX
+\&.de XX
+ps: def
+/BPhook
+{ gsave .9 setgray clippath pathbbox exch 2 copy
+ .5 mul exch .5 mul translate atan rotate pop pop
+ /NewCenturySchlbk-Roman findfont 200 scalefont setfont
+ (DRAFT) dup stringwidth pop \-.5 mul \-70 moveto show
+ grestore }
+def
+\&..
+\&.devicem XX
+.EE
+.RE
+.
+.
+.P
+Or,
+to cause lines and polygons to be drawn with square linecaps and mitered
+linejoins instead of the round linecaps and linejoins normally used by
+.IR grops ,
+use
+.
+.RS
+.EX
+\&.de XX
+ps: def
+/BPhook { 2 setlinecap 0 setlinejoin } def
+\&..
+\&.devicem XX
+.EE
+.RE
+.
+(square linecaps,
+as opposed to butt linecaps
+.RB (\[lq] "0 setlinecap" \[rq]),
+give true corners in boxed tables even though the lines are drawn
+unconnected).
+.
+.
+.\" ====================================================================
+.SS "Encapsulated PostScript"
+.\" ====================================================================
+.
+.I grops
+itself doesn't emit bounding box information.
+.
+The following script,
+.IR groff2eps ,
+produces an EPS file.
+.
+.
+.RS
+.P
+.EX
+#! /bin/sh
+groff \-P\-b16 "$1" > "$1".ps
+gs \-dNOPAUSE \-sDEVICE=bbox \-\- "$1".ps 2> "$1".bbox
+sed \-e "/\[ha]%%Orientation/r $1.bbox" \[rs]
+ \-e "/\[ha]%!PS\-Adobe\-3.0/s/$/ EPSF\-3.0/" "$1".ps > "$1".eps
+rm "$1".ps "$1".bbox
+.EE
+.RE
+.
+.
+.P
+You can then use
+.RB \[lq] "groff2eps foo" \[rq]
+to convert file
+.I foo
+to
+.IR foo.eps .
+.
+.
+.\" ====================================================================
+.SS "TrueType and other font formats"
+.\" ====================================================================
+.
+TrueType fonts can be used with
+.I grops
+if converted first to Type\~42 format,
+a PostScript wrapper equivalent to the PFA format described in
+.MR pfbtops @MAN1EXT@ .
+.
+Several methods exist to generate a Type\~42 wrapper;
+some of them involve the use of a PostScript interpreter such as
+Ghostscript\[em]see
+.MR gs 1 .
+.
+.
+.P
+One approach is to use
+.UR https://fontforge.org/
+FontForge
+.UE ,
+a font editor that can convert most outline font formats.
+.
+Here's an example of using the Roboto Slab Serif font with
+.IR groff .
+.
+Several variables are used so that you can more easily adapt it into
+your own script.
+.
+.
+.RS 4
+.P
+.EX
+MAP=@FONTDIR@/devps/generate/text.map
+TTF=/usr/share/fonts/truetype/roboto/slab/RobotoSlab\-Regular.ttf
+BASE=$(basename \[dq]$TTF\[dq])
+INT=${BASE%.ttf}
+PFA=$INT.pfa
+AFM=$INT.afm
+GFN=RSR
+DIR=$HOME/.local/groff/font
+mkdir \-p \[dq]$DIR\[dq]/devps
+fontforge \-lang=ff \-c \[dq]Open(\[rs]\[dq]$TTF\[rs]\[dq]);\[rs]
+\tGenerate(\[rs]\[dq]$DIR/devps/$PFA\[rs]\[dq]);\[dq]
+afmtodit \[dq]$DIR/devps/$AFM\[dq] \[dq]$MAP\[dq] \
+\[dq]$DIR/devps/$GFN\[dq]
+printf \[dq]$BASE\[rs]t$PFA\[rs]n\[dq] >> \[dq]$DIR/devps/download\[dq]
+.EE
+.RE
+.
+.
+.P
+.I fontforge
+and
+.I afmtodit
+may generate warnings depending on the attributes of the font.
+.
+The test procedure is simple.
+.
+.
+.RS 4
+.P
+.EX
+printf \[dq].ft RSR\[rs]nHello, world!\[rs]n\[dq] | groff \-F \
+\[dq]$DIR\[dq] > hello.ps
+.EE
+.RE
+.
+.
+.P
+Once you're satisfied that the font works,
+you may want to generate any available related styles
+(for instance,
+Roboto Slab
+also has \[lq]Bold\[rq],
+\[lq]Light\[rq],
+and
+\[lq]Thin\[rq]
+styles)
+and set up
+.I GROFF_FONT_PATH
+in your environment to include the directory you keep the generated
+fonts in so that you don't have to use the
+.B \-F
+option.
+.
+.
+.\" ====================================================================
+.SH "Font installation"
+.\" ====================================================================
+.
+The following is a step-by-step font installation guide for
+.I grops.
+.
+.
+.IP \[bu] 2n
+Convert your font to something
+.I groff
+understands.
+.
+This is a PostScript Type\~1 font in PFA format or a PostScript
+Type\~42 font,
+together with an AFM file.
+.
+A PFA file begins as follows.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+%!PS\-AdobeFont\-1.0:
+.EE
+.RE
+.
+A PFB file contains this string as well,
+preceded by some non-printing bytes.
+.
+If your font is in PFB format,
+use
+.IR groff 's
+.MR pfbtops @MAN1EXT@
+program to convert it to PFA.
+.
+For TrueType and other font formats,
+we recommend
+.IR fontforge ,
+which can convert most outline font formats.
+.
+A Type\~42 font file begins as follows.
+.
+.RS
+.EX
+%!PS\-TrueTypeFont
+.EE
+.RE
+.
+This is a wrapper format for TrueType fonts.
+.
+Old PostScript printers might not support them
+(that is,
+they might not have a built-in TrueType font interpreter).
+.
+In the following steps,
+we will consider the use of CTAN's
+.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr
+BrushScriptX-Italic
+.UE
+font in PFA format.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Convert the AFM file to a
+.I groff
+font description file with the
+.MR afmtodit @MAN1EXT@
+program.
+.
+For instance,
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+$ \c
+.B afmtodit BrushScriptX\-Italic.afm text.map BSI
+.EE
+.RE
+.
+converts the Adobe Font Metric file
+.I BrushScriptX\-Italic.afm
+to the
+.I groff
+font description file
+.IR BSI .
+.RE \" now restore left margin
+.
+.
+.IP
+If you have a font family which provides regular upright (roman),
+bold,
+italic,
+and
+bold-italic styles
+(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]),
+we recommend using the letters
+.BR R ,
+.BR B ,
+.BR I ,
+and
+.BR BI ,
+respectively,
+as suffixes to the
+.I groff
+font family name to enable
+.IR groff 's
+font family and style selection features.
+.
+An example is
+.IR groff 's
+built-in support for Times:
+the font family
+name is abbreviated as
+.BR T ,
+and the
+.I groff
+font names are therefore
+.BR TR ,
+.BR TB ,
+.BR TI ,
+and
+.BR TBI .
+.
+In our example,
+however,
+the BrushScriptX font is available in a single style only,
+italic.
+.
+.
+.IP \[bu]
+Install the
+.I groff
+font description file(s) in a
+.I devps
+subdirectory in the search path that
+.I groff
+uses for device and font file descriptions.
+.
+See the
+.I GROFF_FONT_PATH
+entry in section \[lq]Environment\[rq] of
+.MR @g@troff @MAN1EXT@
+for the current value of the font search path.
+.
+While
+.I groff
+doesn't directly use AFM files,
+it is a good idea to store them alongside its font description files.
+.
+.
+.IP \[bu]
+Register fonts in the
+.I devps/download
+file so they can be located for embedding in PostScript files
+.I grops
+generates.
+.
+Only the first
+.I download
+file encountered in the font search path is read.
+.
+If in doubt,
+copy the default
+.I download
+file
+(see section \[lq]Files\[rq] below)
+to the first directory in the font search path and add your fonts there.
+.
+The PostScript font name used by
+.I grops
+is stored in the
+.B internalname
+field in the
+.I groff
+font description file.
+.
+(This name does not necessarily resemble the font's file name.)
+.
+We add the following line to
+.IR download .
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa
+.EE
+.RE \" but only one to get back to it
+.
+A tab character,
+depicted as \[->],
+separates the fields.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Test the selection and embedding of the new font.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \
+| groff \-T ps \-P \-e >hello.ps
+see hello.pdf
+.EE
+.RE
+.RE \" now restore left margin
+.
+.
+.\" ====================================================================
+.SH "Old fonts"
+.\" ====================================================================
+.
+.I groff
+versions 1.19.2 and earlier contained descriptions of a slightly
+different set of the base 35 PostScript level 2 fonts defined by Adobe.
+.
+The older set has 229 glyphs and a larger set of kerning pairs;
+the newer one has 314 glyphs and includes the Euro glyph.
+.
+For backwards compatibility,
+these old font descriptions are also installed in the
+.I @OLDFONTDIR@/\:\%devps
+directory.
+.
+.
+.P
+To use them,
+make sure that
+.I grops
+finds the fonts before the default system fonts
+(with the same names):
+either give
+.I grops
+the
+.B \-F
+command-line option,
+.
+.RS
+.EX
+$ \c
+.B groff \-Tps \-P\-F \-P@OLDFONTDIR@ \c
+\&.\|.\|.
+.EE
+.RE
+.
+or add the directory to
+.IR groff 's
+font and device description search path environment variable,
+.
+.RS
+.EX
+$ \c
+.B GROFF_FONT_PATH=\:@OLDFONTDIR@ \[rs]
+.RS
+.B groff \-Tps \c
+\&.\|.\|.
+.RE
+.EE
+.RE
+.
+when the command runs.
+.
+.
+.br
+.ne 3v
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROPS_PROLOGUE
+If this is set to
+.IR foo ,
+then
+.I grops
+uses the file
+.I foo
+(in the font path) instead of the default prologue file
+.IR prologue .
+.
+The option
+.B \-P
+overrides this environment variable.
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR ctime 3
+and recorded in a PostScript comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:DESC
+describes the
+.B ps
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devps/ F
+describes the font known
+.RI as\~ F
+on device
+.BR ps .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%download
+lists fonts available for embedding within the PostScript document
+(or download to the device).
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%prologue
+is the default PostScript prologue prefixed to every output file.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:text.enc
+describes the encoding scheme used by most PostScript Type\~1 fonts;
+the
+.B \%encoding
+directive of
+font description files for the
+.B ps
+device refers to it.
+.
+.
+.TP
+.I @MACRODIR@/\:ps.tmac
+defines macros for use with the
+.B ps
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B ps
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:pspic.tmac
+defines the
+.B PSPIC
+macro for embedding images in a document;
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+It is automatically loaded by
+.I troffrc.
+.
+.
+.TP
+.I @MACRODIR@/psold.tmac
+provides replacement glyphs for text fonts that lack complete coverage
+of the ISO Latin-1 character set;
+using it,
+.I groff
+can produce glyphs like eth (\[Sd]) and thorn (\[Tp]) that older
+PostScript printers do not natively support.
+.
+.
+.P
+.I grops
+creates temporary files using the template
+.RI \[lq] grops XXXXXX\[rq];
+see
+.MR groff @MAN1EXT@
+for details on their storage location.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR http://\:partners\:.adobe\:.com/\:public/\:developer/\:en/\:ps/\
+\:5001\:.DSC_Spec\:.pdf
+PostScript Language Document Structuring Conventions Specification
+.UE
+.
+.
+.P
+.MR afmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR pfbtops @MAN1EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_tmac @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grops_1_man_C]
+.do rr *groff_grops_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grops/grops.am b/src/devices/grops/grops.am
new file mode 100644
index 0000000..cb5532a
--- /dev/null
+++ b/src/devices/grops/grops.am
@@ -0,0 +1,38 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 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/>.
+
+bin_PROGRAMS += grops
+grops_SOURCES = \
+ src/devices/grops/ps.cpp \
+ src/devices/grops/psrm.cpp \
+ src/devices/grops/ps.h
+grops_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grops/grops.1
+EXTRA_DIST += \
+ src/devices/grops/grops.1.man \
+ src/devices/grops/psfig.diff \
+ src/devices/grops/TODO
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grops/ps.cpp b/src/devices/grops/ps.cpp
new file mode 100644
index 0000000..807945f
--- /dev/null
+++ b/src/devices/grops/ps.cpp
@@ -0,0 +1,1894 @@
+/* Copyright (C) 1989-2020 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 "lib.h" // PI
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "nonposix.h"
+#include "paper.h"
+#include "curtime.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) / double(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()
+{
+ free(encoding);
+ 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()
+{
+ 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("device horizontal motion quantum must be 1, got %1",
+ font::hor);
+ if (font::vert != 1)
+ fatal("device vertical motion quantum must be 1, got %1",
+ font::vert);
+ if (font::res % (font::sizescale*72) != 0)
+ fatal("device resolution must be a multiple of 72*'sizescale', got"
+ " %1 ('sizescale'=%2)", font::res, font::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++;
+ }
+ free(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]);
+ 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);
+ }
+ }
+ delete[] done_encoding;
+}
+
+void ps_printer::encode_subfont(subencoding *sub)
+{
+ assert(sub != 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 dimensions are defined as a pair of 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 = current_time();
+ 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 const 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) {
+ flush_sbuf();
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+ (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))
+ 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)
+{
+ 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 *)
+{
+ 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 *)
+{
+ 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)
+{
+ 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 units 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 '%1'", 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("ignoring invalid custom paper format '%1'", 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("invalid line width '%1' ignored", 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 [-glm] [-b brokenness-flags] [-c num-copies]"
+" [-F font-directory] [-I inclusion-directory] [-p paper-format]"
+" [-P prologue-file] [-w rule-thickness] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream)
+ fputs(
+"\n"
+"Translate the output of troff(1) into PostScript. See the grops(1)\n"
+"manual page.\n",
+ stream);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grops/ps.h b/src/devices/grops/ps.h
new file mode 100644
index 0000000..5cef694
--- /dev/null
+++ b/src/devices/grops/ps.h
@@ -0,0 +1,129 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 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/>. */
+
+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_color(unsigned int);
+ 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 *);
+ FILE *get_file();
+private:
+ FILE *fp;
+ int col;
+ int max_line_length; // not including newline
+ int need_space;
+ int fixed_point;
+};
+
+inline FILE *ps_output::get_file()
+{
+ return fp;
+}
+
+// this must stay in sync with 'resource_table' in 'psrm.cpp'
+enum resource_type {
+ RESOURCE_FONT,
+ RESOURCE_FONTSET,
+ RESOURCE_PROCSET,
+ RESOURCE_FILE,
+ RESOURCE_ENCODING,
+ RESOURCE_FORM,
+ RESOURCE_PATTERN
+};
+
+struct resource;
+
+extern string an_empty_string;
+
+class resource_manager {
+public:
+ resource_manager();
+ ~resource_manager();
+ void import_file(const char *filename, ps_output &);
+ void need_font(const char *name);
+ void print_header_comments(ps_output &);
+ void document_setup(ps_output &);
+ void output_prolog(ps_output &);
+private:
+ unsigned extensions;
+ unsigned language_level;
+ resource *procset_resource;
+ resource *resource_list;
+ resource *lookup_resource(resource_type type, string &name,
+ string &version = an_empty_string,
+ unsigned revision = 0);
+ resource *lookup_font(const char *name);
+ void read_download_file();
+ void supply_resource(resource *r, int rank, FILE *outfp,
+ int is_document = 0);
+ void process_file(int rank, FILE *fp, const char *filename, FILE *outfp);
+ resource *read_file_arg(const char **);
+ resource *read_procset_arg(const char **);
+ resource *read_font_arg(const char **);
+ resource *read_resource_arg(const char **);
+ void print_resources_comment(unsigned flag, FILE *outfp);
+ void print_extensions_comment(FILE *outfp);
+ void print_language_level_comment(FILE *outfp);
+ int do_begin_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_document(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_document(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_procset(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_procset(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_font(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_font(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_file(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_file(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int change_to_end_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_preview(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_data(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_binary(const char *ptr, int rank, FILE *fp, FILE *outfp);
+};
+
+extern unsigned broken_flags;
+
+// broken_flags is ored from these
+
+enum {
+ NO_SETUP_SECTION = 01,
+ STRIP_PERCENT_BANG = 02,
+ STRIP_STRUCTURE_COMMENTS = 04,
+ USE_PS_ADOBE_2_0 = 010,
+ NO_PAPERSIZE = 020
+};
+
+#include "searchpath.h"
+
+extern search_path include_search_path;
diff --git a/src/devices/grops/psfig.diff b/src/devices/grops/psfig.diff
new file mode 100644
index 0000000..4e0d1f9
--- /dev/null
+++ b/src/devices/grops/psfig.diff
@@ -0,0 +1,106 @@
+These are patches to makes psfig work with groff. They apply to the
+version of psfig in comp.sources.unix/Volume11. After applying them,
+psfig should be recompiled with -DGROFF. The resulting psfig will
+work only with groff, so you might want to install it under a
+different name. The output of this psfig must be processed using the
+macros in the file ../tmac/tmac.psfig. These will automatically add
+the necessary PostScript code to the prologue output by grops. Use of
+the 'global' feature in psfig will result in non-conformant PostScript
+which will fail if processed by a page reversal program. Note that
+psfig is unsupported by me (I'm not interested in hearing about psfig
+problems.) For new documents, I recommend using the PostScript
+inclusion features provided by grops.
+
+James Clark
+jjc@jclark.com
+
+*** cmds.c.~1~ Thu Feb 14 16:09:45 1991
+--- cmds.c Mon Mar 4 12:49:26 1991
+***************
+*** 245,253 ****
+--- 245,261 ----
+ (void) sprintf(x, "%.2fp", fx);
+ (void) sprintf(y, "%.2fp", fy);
+ } else if (!*x) {
++ #ifndef GROFF
+ (void) sprintf(x,"(%.2fp*%s/%.2fp)", fx, y, fy);
++ #else /* GROFF */
++ (void) sprintf(x,"(%.0fu*%s/%.0fu)", fx, y, fy);
++ #endif /* GROFF */
+ } else if (!*y) {
++ #ifndef GROFF
+ (void) sprintf(y,"(%.2fp*%s/%.2fp)", fy, x, fx);
++ #else /* GROFF */
++ (void) sprintf(y,"(%.0fu*%s/%.0fu)", fy, x, fx);
++ #endif /* GROFF */
+ }
+
+ /*
+*** troff.c.~1~ Thu Feb 14 16:09:48 1991
+--- troff.c Mon Mar 4 12:48:46 1991
+***************
+*** 26,32 ****
+--- 26,36 ----
+ }
+
+
++ #ifndef GROFF
+ char incl_file_s[] = "\\X'f%s'";
++ #else /* GROFF */
++ char incl_file_s[] = "\\X'ps: file %s'";
++ #endif /* GROFF */
+ includeFile(filenm)
+ char *filenm; {
+ printf(incl_file_s, filenm);
+***************
+*** 40,52 ****
+--- 44,64 ----
+ error("buffer overflow");
+ }
+
++ #ifndef GROFF
+ char endfig_s[] = "\\X'pendFig'";
++ #else /* GROFF */
++ char endfig_s[] = "\\X'ps: exec psfigend'";
++ #endif /* GROFF */
+ endfig() {
+ printf(endfig_s);
+ }
+
+ char startfig_s[] =
++ #ifndef GROFF
+ "\\X'p\\w@\\h@%s@@'\\X'p\\w@\\h@%s@@'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'pstartFig'";
++ #else /* GROFF */
++ "\\X'ps: exec \\w@\\h@%s@@ \\w@\\h@%s@@ %.2f %.2f %.2f %.2f psfigstart'";
++ #endif /* GROFF */
+
+ startfig(x, y, llx, lly, urx, ury)
+ char *x, *y;
+***************
+*** 57,63 ****
+--- 69,79 ----
+ }
+
+ emitDoClip() {
++ #ifndef GROFF
+ printf("\\X'pdoclip'");
++ #else /* GROFF */
++ printf("\\X'ps: exec psfigclip'");
++ #endif /* GROFF */
+ }
+
+ flushX()
+***************
+*** 116,122 ****
+--- 132,142 ----
+
+ #define isWhite(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n')
+
++ #ifndef GROFF
+ char literal_s[] = "\\X'p%s'";
++ #else /* GROFF */
++ char literal_s[] = "\\X'ps: exec %s'";
++ #endif /* GROFF */
+ emitLiteral(text)
+ char *text; {
+ static char litbuf[BUFSZ];
diff --git a/src/devices/grops/psrm.cpp b/src/devices/grops/psrm.cpp
new file mode 100644
index 0000000..3c9a8b7
--- /dev/null
+++ b/src/devices/grops/psrm.cpp
@@ -0,0 +1,1189 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#include "ps.h"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+#define GROPS_PROLOGUE "prologue"
+
+static void print_ps_string(const string &s, FILE *outfp);
+
+cset white_space("\n\r \t\f");
+string an_empty_string;
+
+char valid_input_table[256]= {
+#ifndef IS_EBCDIC_HOST
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+#else
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+#endif
+};
+
+const char *extension_table[] = {
+ "DPS",
+ "CMYK",
+ "Composite",
+ "FileSystem",
+};
+
+const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);
+
+// this must stay in sync with 'resource_type' in 'ps.h'
+const char *resource_table[] = {
+ "font",
+ "fontset",
+ "procset",
+ "file",
+ "encoding",
+ "form",
+ "pattern",
+};
+
+const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);
+
+static int read_uint_arg(const char **pp, unsigned *res)
+{
+ while (white_space(**pp))
+ *pp += 1;
+ if (**pp == '\0') {
+ error("missing argument");
+ return 0;
+ }
+ const char *start = *pp;
+ // XXX use strtoul
+ long n = strtol(start, (char **)pp, 10);
+ if (n == 0 && *pp == start) {
+ error("not an integer");
+ return 0;
+ }
+ if (n < 0) {
+ error("argument must not be negative");
+ return 0;
+ }
+ *res = unsigned(n);
+ return 1;
+}
+
+struct resource {
+ resource *next;
+ resource_type type;
+ string name;
+ enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 };
+ unsigned flags;
+ string version;
+ unsigned revision;
+ char *filename;
+ int rank;
+ resource(resource_type, string &, string & = an_empty_string, unsigned = 0);
+ ~resource();
+ void print_type_and_name(FILE *outfp);
+};
+
+resource::resource(resource_type t, string &n, string &v, unsigned r)
+: next(0), type(t), flags(0), revision(r), filename(0), rank(-1)
+{
+ name.move(n);
+ version.move(v);
+ if (type == RESOURCE_FILE) {
+ if (name.search('\0') >= 0)
+ error("filename contains a character with code 0");
+ filename = name.extract();
+ }
+}
+
+resource::~resource()
+{
+ free(filename);
+}
+
+void resource::print_type_and_name(FILE *outfp)
+{
+ fputs(resource_table[type], outfp);
+ putc(' ', outfp);
+ print_ps_string(name, outfp);
+ if (type == RESOURCE_PROCSET) {
+ putc(' ', outfp);
+ print_ps_string(version, outfp);
+ fprintf(outfp, " %u", revision);
+ }
+}
+
+resource_manager::resource_manager()
+: extensions(0), language_level(0), resource_list(0)
+{
+ read_download_file();
+ string procset_name("grops");
+ extern const char *version_string;
+ extern const char *revision_string;
+ unsigned revision_uint;
+ if (!read_uint_arg(&revision_string, &revision_uint))
+ revision_uint = 0;
+ string procset_version(version_string);
+ procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name,
+ procset_version, revision_uint);
+ procset_resource->flags |= resource::SUPPLIED;
+}
+
+resource_manager::~resource_manager()
+{
+ while (resource_list) {
+ resource *tem = resource_list;
+ resource_list = resource_list->next;
+ delete tem;
+ }
+}
+
+resource *resource_manager::lookup_resource(resource_type type,
+ string &name,
+ string &version,
+ unsigned revision)
+{
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->type == type
+ && r->name == name
+ && r->version == version
+ && r->revision == revision)
+ return r;
+ r = new resource(type, name, version, revision);
+ r->next = resource_list;
+ resource_list = r;
+ return r;
+}
+
+// Just a specialized version of lookup_resource().
+
+resource *resource_manager::lookup_font(const char *name)
+{
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT
+ && strlen(name) == (size_t)r->name.length()
+ && memcmp(name, r->name.contents(), r->name.length()) == 0)
+ return r;
+ string s(name);
+ r = new resource(RESOURCE_FONT, s);
+ r->next = resource_list;
+ resource_list = r;
+ return r;
+}
+
+void resource_manager::need_font(const char *name)
+{
+ lookup_font(name)->flags |= resource::FONT_NEEDED;
+}
+
+typedef resource *Presource; // Work around g++ bug.
+
+void resource_manager::document_setup(ps_output &out)
+{
+ int nranks = 0;
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->rank >= nranks)
+ nranks = r->rank + 1;
+ if (nranks > 0) {
+ // Sort resource_list in reverse order of rank.
+ Presource *head = new Presource[nranks + 1];
+ Presource **tail = new Presource *[nranks + 1];
+ int i;
+ for (i = 0; i < nranks + 1; i++) {
+ head[i] = 0;
+ tail[i] = &head[i];
+ }
+ for (r = resource_list; r; r = r->next) {
+ i = r->rank < 0 ? 0 : r->rank + 1;
+ *tail[i] = r;
+ tail[i] = &(*tail[i])->next;
+ }
+ resource_list = 0;
+ for (i = 0; i < nranks + 1; i++)
+ if (head[i]) {
+ *tail[i] = resource_list;
+ resource_list = head[i];
+ }
+ delete[] head;
+ delete[] tail;
+ // check it
+ for (r = resource_list; r; r = r->next)
+ if (r->next)
+ assert(r->rank >= r->next->rank);
+ for (r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT && r->rank >= 0)
+ supply_resource(r, -1, out.get_file());
+ }
+}
+
+void resource_manager::print_resources_comment(unsigned flag,
+ FILE *outfp)
+{
+ int continued = 0;
+ for (resource *r = resource_list; r; r = r->next)
+ if (r->flags & flag) {
+ if (continued)
+ fputs("%%+ ", outfp);
+ else {
+ fputs(flag == resource::NEEDED
+ ? "%%DocumentNeededResources: "
+ : "%%DocumentSuppliedResources: ",
+ outfp);
+ continued = 1;
+ }
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+}
+
+void resource_manager::print_header_comments(ps_output &out)
+{
+ for (resource *r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED))
+ supply_resource(r, 0, 0);
+ print_resources_comment(resource::NEEDED, out.get_file());
+ print_resources_comment(resource::SUPPLIED, out.get_file());
+ print_language_level_comment(out.get_file());
+ print_extensions_comment(out.get_file());
+}
+
+void resource_manager::output_prolog(ps_output &out)
+{
+ FILE *outfp = out.get_file();
+ out.end_line();
+ char *path;
+ if (!getenv("GROPS_PROLOGUE")) {
+ string e = "GROPS_PROLOGUE";
+ e += '=';
+ e += GROPS_PROLOGUE;
+ e += '\0';
+ if (putenv(strsave(e.contents())))
+ fatal("putenv failed");
+ }
+ char *prologue = getenv("GROPS_PROLOGUE");
+ FILE *fp = font::open_file(prologue, &path);
+ if (!fp)
+ fatal("failed to open PostScript prologue '%1': %2", prologue,
+ strerror(errno));
+ fputs("%%BeginResource: ", outfp);
+ procset_resource->print_type_and_name(outfp);
+ putc('\n', outfp);
+ process_file(-1, fp, path, outfp);
+ fclose(fp);
+ free(path);
+ fputs("%%EndResource\n", outfp);
+}
+
+void resource_manager::import_file(const char *filename, ps_output &out)
+{
+ out.end_line();
+ string name(filename);
+ resource *r = lookup_resource(RESOURCE_FILE, name);
+ supply_resource(r, -1, out.get_file(), 1);
+}
+
+void resource_manager::supply_resource(resource *r, int rank,
+ FILE *outfp, int is_document)
+{
+ if (r->flags & resource::BUSY) {
+ r->name += '\0';
+ fatal("loop detected in dependency graph for %1 '%2'",
+ resource_table[r->type],
+ r->name.contents());
+ }
+ r->flags |= resource::BUSY;
+ if (rank > r->rank)
+ r->rank = rank;
+ char *path = 0 /* nullptr */;
+ FILE *fp = 0 /* nullptr */;
+ if (r->filename != 0 /* nullptr */) {
+ if (r->type == RESOURCE_FONT) {
+ fp = font::open_file(r->filename, &path);
+ if (!fp) {
+ error("failed to open PostScript resource '%1': %2",
+ r->filename, strerror(errno));
+ delete[] r->filename;
+ r->filename = 0 /* nullptr */;
+ }
+ }
+ else {
+ errno = 0;
+ fp = include_search_path.open_file_cautious(r->filename);
+ if (!fp) {
+ error("can't open '%1': %2", r->filename, strerror(errno));
+ delete[] r->filename;
+ r->filename = 0 /* nullptr */;
+ }
+ else
+ path = r->filename;
+ }
+ }
+ if (fp) {
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document) {
+ fputs("%%BeginDocument: ", outfp);
+ print_ps_string(r->name, outfp);
+ putc('\n', outfp);
+ }
+ else {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ process_file(rank, fp, path, outfp);
+ fclose(fp);
+ if (r->type == RESOURCE_FONT)
+ free(path);
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document)
+ fputs("%%EndDocument\n", outfp);
+ else
+ fputs("%%EndResource\n", outfp);
+ }
+ r->flags |= resource::SUPPLIED;
+ }
+ else {
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document) {
+ fputs("%%IncludeDocument: ", outfp);
+ print_ps_string(r->name, outfp);
+ putc('\n', outfp);
+ }
+ else {
+ fputs("%%IncludeResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ r->flags |= resource::NEEDED;
+ }
+ r->flags &= ~resource::BUSY;
+}
+
+#define PS_MAGIC "%!PS-Adobe-"
+
+static int ps_get_line(string &buf, FILE *fp)
+{
+ buf.clear();
+ int c = getc(fp);
+ if (c == EOF)
+ return 0;
+ current_lineno++;
+ while (c != '\r' && c != '\n' && c != EOF) {
+ if (!valid_input_table[c])
+ error("invalid input character code %1", int(c));
+ buf += c;
+ c = getc(fp);
+ }
+ buf += '\n';
+ buf += '\0';
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != EOF && c != '\n')
+ ungetc(c, fp);
+ }
+ return 1;
+}
+
+static int read_text_arg(const char **pp, string &res)
+{
+ res.clear();
+ while (white_space(**pp))
+ *pp += 1;
+ if (**pp == '\0') {
+ error("missing argument");
+ return 0;
+ }
+ if (**pp != '(') {
+ for (; **pp != '\0' && !white_space(**pp); *pp += 1)
+ res += **pp;
+ return 1;
+ }
+ *pp += 1;
+ res.clear();
+ int level = 0;
+ for (;;) {
+ if (**pp == '\0' || **pp == '\r' || **pp == '\n') {
+ error("missing ')'");
+ return 0;
+ }
+ if (**pp == ')') {
+ if (level == 0) {
+ *pp += 1;
+ break;
+ }
+ res += **pp;
+ level--;
+ }
+ else if (**pp == '(') {
+ level++;
+ res += **pp;
+ }
+ else if (**pp == '\\') {
+ *pp += 1;
+ switch (**pp) {
+ case 'n':
+ res += '\n';
+ break;
+ case 'r':
+ res += '\n';
+ break;
+ case 't':
+ res += '\t';
+ break;
+ case 'b':
+ res += '\b';
+ break;
+ case 'f':
+ res += '\f';
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int val = **pp - '0';
+ if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
+ *pp += 1;
+ val = val*8 + (**pp - '0');
+ if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
+ *pp += 1;
+ val = val*8 + (**pp - '0');
+ }
+ }
+ }
+ break;
+ default:
+ res += **pp;
+ break;
+ }
+ }
+ else
+ res += **pp;
+ *pp += 1;
+ }
+ return 1;
+}
+
+resource *resource_manager::read_file_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(RESOURCE_FILE, arg);
+}
+
+resource *resource_manager::read_font_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(RESOURCE_FONT, arg);
+}
+
+resource *resource_manager::read_procset_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ string version;
+ if (!read_text_arg(ptr, version))
+ return 0;
+ unsigned revision;
+ if (!read_uint_arg(ptr, &revision))
+ return 0;
+ return lookup_resource(RESOURCE_PROCSET, arg, version, revision);
+}
+
+resource *resource_manager::read_resource_arg(const char **ptr)
+{
+ while (white_space(**ptr))
+ *ptr += 1;
+ const char *name = *ptr;
+ while (**ptr != '\0' && !white_space(**ptr))
+ *ptr += 1;
+ if (name == *ptr) {
+ error("missing resource type");
+ return 0;
+ }
+ int ri;
+ for (ri = 0; ri < NRESOURCES; ri++)
+ if (strlen(resource_table[ri]) == size_t(*ptr - name)
+ && strncasecmp(resource_table[ri], name, *ptr - name) == 0)
+ break;
+ if (ri >= NRESOURCES) {
+ error("unknown resource type");
+ return 0;
+ }
+ if (ri == RESOURCE_PROCSET)
+ return read_procset_arg(ptr);
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(resource_type(ri), arg);
+}
+
+static const char *matches_comment(string &buf, const char *comment)
+{
+ if ((size_t)buf.length() < strlen(comment) + 3)
+ return 0;
+ if (buf[0] != '%' || buf[1] != '%')
+ return 0;
+ const char *bufp = buf.contents() + 2;
+ for (; *comment; comment++, bufp++)
+ if (*bufp != *comment)
+ return 0;
+ if (comment[-1] == ':')
+ return bufp;
+ if (*bufp == '\0' || white_space(*bufp))
+ return bufp;
+ return 0;
+}
+
+// Return 1 if the line should be copied out.
+
+int resource_manager::do_begin_resource(const char *ptr, int, FILE *,
+ FILE *)
+{
+ resource *r = read_resource_arg(&ptr);
+ if (r)
+ r->flags |= resource::SUPPLIED;
+ return 1;
+}
+
+int resource_manager::do_include_resource(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_resource_arg(&ptr);
+ if (r) {
+ if (r->type == RESOURCE_FONT) {
+ if (rank >= 0)
+ supply_resource(r, rank + 1, outfp);
+ else
+ r->flags |= resource::FONT_NEEDED;
+ }
+ else
+ supply_resource(r, rank, outfp);
+ }
+ return 0;
+}
+
+int resource_manager::do_begin_document(const char *ptr, int, FILE *,
+ FILE *)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ r->flags |= resource::SUPPLIED;
+ return 1;
+}
+
+int resource_manager::do_include_document(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp, 1);
+ return 0;
+}
+
+int resource_manager::do_begin_procset(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_procset_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_procset(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_procset_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_file(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_file(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_font(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_font_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_font(const char *ptr, int rank, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_font_arg(&ptr);
+ if (r) {
+ if (rank >= 0)
+ supply_resource(r, rank + 1, outfp);
+ else
+ r->flags |= resource::FONT_NEEDED;
+ }
+ return 0;
+}
+
+int resource_manager::change_to_end_resource(const char *, int, FILE *,
+ FILE *outfp)
+{
+ if (outfp)
+ fputs("%%EndResource\n", outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_preview(const char *, int, FILE *fp,
+ FILE *)
+{
+ string buf;
+ do {
+ if (!ps_get_line(buf, fp)) {
+ error("end of file in preview section");
+ break;
+ }
+ } while (!matches_comment(buf, "EndPreview"));
+ return 0;
+}
+
+int read_one_of(const char **ptr, const char **s, int n)
+{
+ while (white_space(**ptr))
+ *ptr += 1;
+ if (**ptr == '\0')
+ return -1;
+ const char *start = *ptr;
+ do {
+ ++(*ptr);
+ } while (**ptr != '\0' && !white_space(**ptr));
+ for (int i = 0; i < n; i++)
+ if (strlen(s[i]) == size_t(*ptr - start)
+ && memcmp(s[i], start, *ptr - start) == 0)
+ return i;
+ return -1;
+}
+
+void skip_possible_newline(FILE *fp, FILE *outfp)
+{
+ int c = getc(fp);
+ if (c == '\r') {
+ current_lineno++;
+ if (outfp)
+ putc(c, outfp);
+ int cc = getc(fp);
+ if (cc != '\n') {
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else {
+ if (outfp)
+ putc(cc, outfp);
+ }
+ }
+ else if (c == '\n') {
+ current_lineno++;
+ if (outfp)
+ putc(c, outfp);
+ }
+ else if (c != EOF)
+ ungetc(c, fp);
+}
+
+int resource_manager::do_begin_data(const char *ptr, int, FILE *fp,
+ FILE *outfp)
+{
+ while (white_space(*ptr))
+ ptr++;
+ const char *start = ptr;
+ unsigned numberof;
+ if (!read_uint_arg(&ptr, &numberof))
+ return 0;
+ static const char *types[] = { "Binary", "Hex", "ASCII" };
+ const int Binary = 0;
+ int type = 0;
+ static const char *units[] = { "Bytes", "Lines" };
+ const int Bytes = 0;
+ int unit = Bytes;
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr != '\0') {
+ type = read_one_of(&ptr, types, 3);
+ if (type < 0) {
+ error("bad data type");
+ return 0;
+ }
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr != '\0') {
+ unit = read_one_of(&ptr, units, 2);
+ if (unit < 0) {
+ error("expected 'Bytes' or 'Lines'");
+ return 0;
+ }
+ }
+ }
+ if (type != Binary)
+ return 1;
+ if (outfp) {
+ fputs("%%BeginData: ", outfp);
+ fputs(start, outfp);
+ }
+ if (numberof > 0) {
+ unsigned bytecount = 0;
+ unsigned linecount = 0;
+ do {
+ int c = getc(fp);
+ if (c == EOF) {
+ error("end of file within data section");
+ return 0;
+ }
+ if (outfp)
+ putc(c, outfp);
+ bytecount++;
+ if (c == '\r') {
+ int cc = getc(fp);
+ if (cc != '\n') {
+ linecount++;
+ current_lineno++;
+ }
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else if (c == '\n') {
+ linecount++;
+ current_lineno++;
+ }
+ } while ((unit == Bytes ? bytecount : linecount) < numberof);
+ }
+ skip_possible_newline(fp, outfp);
+ string buf;
+ if (!ps_get_line(buf, fp)) {
+ error("missing %%%%EndData line");
+ return 0;
+ }
+ if (!matches_comment(buf, "EndData"))
+ error("bad %%%%EndData line");
+ if (outfp)
+ fputs(buf.contents(), outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp,
+ FILE *outfp)
+{
+ if (!outfp)
+ return 0;
+ unsigned count;
+ if (!read_uint_arg(&ptr, &count))
+ return 0;
+ if (outfp)
+ fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count);
+ while (count != 0) {
+ int c = getc(fp);
+ if (c == EOF) {
+ error("end of file within binary section");
+ return 0;
+ }
+ if (outfp)
+ putc(c, outfp);
+ --count;
+ if (c == '\r') {
+ int cc = getc(fp);
+ if (cc != '\n')
+ current_lineno++;
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else if (c == '\n')
+ current_lineno++;
+ }
+ skip_possible_newline(fp, outfp);
+ string buf;
+ if (!ps_get_line(buf, fp)) {
+ error("missing %%%%EndBinary line");
+ return 0;
+ }
+ if (!matches_comment(buf, "EndBinary")) {
+ error("bad %%%%EndBinary line");
+ if (outfp)
+ fputs(buf.contents(), outfp);
+ }
+ else if (outfp)
+ fputs("%%EndData\n", outfp);
+ return 0;
+}
+
+static unsigned parse_extensions(const char *ptr)
+{
+ unsigned flags = 0;
+ for (;;) {
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr == '\0')
+ break;
+ const char *name = ptr;
+ do {
+ ++ptr;
+ } while (*ptr != '\0' && !white_space(*ptr));
+ int i;
+ for (i = 0; i < NEXTENSIONS; i++)
+ if (strlen(extension_table[i]) == size_t(ptr - name)
+ && memcmp(extension_table[i], name, ptr - name) == 0) {
+ flags |= (1 << i);
+ break;
+ }
+ if (i >= NEXTENSIONS) {
+ string s(name, ptr - name);
+ s += '\0';
+ error("unknown extension '%1'", s.contents());
+ }
+ }
+ return flags;
+}
+
+// XXX if it has not been surrounded with {Begin,End}Document need to
+// strip out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections.
+
+// XXX Perhaps the decision whether to use BeginDocument or
+// BeginResource: file should be postponed till we have seen
+// the first line of the file.
+
+void resource_manager::process_file(int rank, FILE *fp,
+ const char *filename, FILE *outfp)
+{
+ // If none of these comments appear in the header section, and we are
+ // just analyzing the file (i.e., outfp is 0), then we can return
+ // immediately.
+ static const char *header_comment_table[] = {
+ "DocumentNeededResources:",
+ "DocumentSuppliedResources:",
+ "DocumentNeededFonts:",
+ "DocumentSuppliedFonts:",
+ "DocumentNeededProcSets:",
+ "DocumentSuppliedProcSets:",
+ "DocumentNeededFiles:",
+ "DocumentSuppliedFiles:",
+ };
+
+ const int NHEADER_COMMENTS = sizeof(header_comment_table)
+ / sizeof(header_comment_table[0]);
+ struct comment_info {
+ const char *name;
+ int (resource_manager::*proc)(const char *, int, FILE *, FILE *);
+ };
+
+ static const comment_info comment_table[] = {
+ { "BeginResource:", &resource_manager::do_begin_resource },
+ { "IncludeResource:", &resource_manager::do_include_resource },
+ { "BeginDocument:", &resource_manager::do_begin_document },
+ { "IncludeDocument:", &resource_manager::do_include_document },
+ { "BeginProcSet:", &resource_manager::do_begin_procset },
+ { "IncludeProcSet:", &resource_manager::do_include_procset },
+ { "BeginFont:", &resource_manager::do_begin_font },
+ { "IncludeFont:", &resource_manager::do_include_font },
+ { "BeginFile:", &resource_manager::do_begin_file },
+ { "IncludeFile:", &resource_manager::do_include_file },
+ { "EndProcSet", &resource_manager::change_to_end_resource },
+ { "EndFont", &resource_manager::change_to_end_resource },
+ { "EndFile", &resource_manager::change_to_end_resource },
+ { "BeginPreview:", &resource_manager::do_begin_preview },
+ { "BeginData:", &resource_manager::do_begin_data },
+ { "BeginBinary:", &resource_manager::do_begin_binary },
+ };
+
+ const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]);
+ string buf;
+ int saved_lineno = current_lineno;
+ const char *saved_filename = current_filename;
+ current_filename = filename;
+ current_lineno = 0;
+ if (!ps_get_line(buf, fp)) {
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+ return;
+ }
+ if ((size_t)buf.length() < sizeof(PS_MAGIC)
+ || memcmp(buf.contents(), PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) {
+ if (outfp) {
+ do {
+ if (!(broken_flags & STRIP_PERCENT_BANG)
+ || buf[0] != '%' || buf[1] != '!')
+ fputs(buf.contents(), outfp);
+ } while (ps_get_line(buf, fp));
+ }
+ }
+ else {
+ if (!(broken_flags & STRIP_PERCENT_BANG) && outfp)
+ fputs(buf.contents(), outfp);
+ int in_header = 1;
+ int interesting = 0;
+ int had_extensions_comment = 0;
+ int had_language_level_comment = 0;
+ for (;;) {
+ if (!ps_get_line(buf, fp))
+ break;
+ int copy_this_line = 1;
+ if (buf[0] == '%') {
+ if (buf[1] == '%') {
+ const char *ptr;
+ int i;
+ for (i = 0; i < NCOMMENTS; i++)
+ if ((ptr = matches_comment(buf, comment_table[i].name))) {
+ copy_this_line
+ = (this->*(comment_table[i].proc))(ptr, rank, fp, outfp);
+ break;
+ }
+ if (i >= NCOMMENTS && in_header) {
+ if ((ptr = matches_comment(buf, "EndComments")))
+ in_header = 0;
+ else if (!had_extensions_comment
+ && (ptr = matches_comment(buf, "Extensions:"))) {
+ extensions |= parse_extensions(ptr);
+ // XXX handle possibility that next line is %%+
+ had_extensions_comment = 1;
+ }
+ else if (!had_language_level_comment
+ && (ptr = matches_comment(buf, "LanguageLevel:"))) {
+ unsigned ll;
+ if (read_uint_arg(&ptr, &ll) && ll > language_level)
+ language_level = ll;
+ had_language_level_comment = 1;
+ }
+ else {
+ for (i = 0; i < NHEADER_COMMENTS; i++)
+ if (matches_comment(buf, header_comment_table[i])) {
+ interesting = 1;
+ break;
+ }
+ }
+ }
+ if ((broken_flags & STRIP_STRUCTURE_COMMENTS)
+ && (matches_comment(buf, "EndProlog")
+ || matches_comment(buf, "Page:")
+ || matches_comment(buf, "Trailer")))
+ copy_this_line = 0;
+ }
+ else if (buf[1] == '!') {
+ if (broken_flags & STRIP_PERCENT_BANG)
+ copy_this_line = 0;
+ }
+ }
+ else
+ in_header = 0;
+ if (!outfp && !in_header && !interesting)
+ break;
+ if (copy_this_line && outfp)
+ fputs(buf.contents(), outfp);
+ }
+ }
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+}
+
+void resource_manager::read_download_file()
+{
+ char *path = 0 /* nullptr */;
+ FILE *fp = font::open_file("download", &path);
+ if (0 /* nullptr */ == fp)
+ fatal("failed to open 'download' file: %1", strerror(errno));
+ char buf[512];
+ int lineno = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ lineno++;
+ char *p = strtok(buf, " \t\r\n");
+ if (p == 0 /* nullptr */ || *p == '#')
+ continue;
+ char *q = strtok(0 /* nullptr */, " \t\r\n");
+ if (!q)
+ fatal_with_file_and_line(path, lineno, "file name missing for"
+ " font '%1'", p);
+ lookup_font(p)->filename = strsave(q);
+ }
+ free(path);
+ fclose(fp);
+}
+
+// XXX Can we share some code with ps_output::put_string()?
+
+static void print_ps_string(const string &s, FILE *outfp)
+{
+ int len = s.length();
+ const char *str = s.contents();
+ int funny = 0;
+ if (str[0] == '(')
+ funny = 1;
+ else {
+ for (int i = 0; i < len; i++)
+ if (str[i] <= 040 || str[i] > 0176) {
+ funny = 1;
+ break;
+ }
+ }
+ if (!funny) {
+ put_string(s, outfp);
+ return;
+ }
+ int level = 0;
+ int i;
+ for (i = 0; i < len; i++)
+ if (str[i] == '(')
+ level++;
+ else if (str[i] == ')' && --level < 0)
+ break;
+ putc('(', outfp);
+ for (i = 0; i < len; i++)
+ switch (str[i]) {
+ case '(':
+ case ')':
+ if (level != 0)
+ putc('\\', outfp);
+ putc(str[i], outfp);
+ break;
+ case '\\':
+ fputs("\\\\", outfp);
+ break;
+ case '\n':
+ fputs("\\n", outfp);
+ break;
+ case '\r':
+ fputs("\\r", outfp);
+ break;
+ case '\t':
+ fputs("\\t", outfp);
+ break;
+ case '\b':
+ fputs("\\b", outfp);
+ break;
+ case '\f':
+ fputs("\\f", outfp);
+ break;
+ default:
+ if (str[i] < 040 || str[i] > 0176)
+ fprintf(outfp, "\\%03o", str[i] & 0377);
+ else
+ putc(str[i], outfp);
+ break;
+ }
+ putc(')', outfp);
+}
+
+void resource_manager::print_extensions_comment(FILE *outfp)
+{
+ if (extensions) {
+ fputs("%%Extensions:", outfp);
+ for (int i = 0; i < NEXTENSIONS; i++)
+ if (extensions & (1 << i)) {
+ putc(' ', outfp);
+ fputs(extension_table[i], outfp);
+ }
+ putc('\n', outfp);
+ }
+}
+
+void resource_manager::print_language_level_comment(FILE *outfp)
+{
+ if (language_level)
+ fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: