diff options
Diffstat (limited to 'src/devices/grops')
-rw-r--r-- | src/devices/grops/TODO | 24 | ||||
-rw-r--r-- | src/devices/grops/grops.1.man | 1831 | ||||
-rw-r--r-- | src/devices/grops/grops.am | 38 | ||||
-rw-r--r-- | src/devices/grops/ps.cpp | 1894 | ||||
-rw-r--r-- | src/devices/grops/ps.h | 129 | ||||
-rw-r--r-- | src/devices/grops/psfig.diff | 106 | ||||
-rw-r--r-- | src/devices/grops/psrm.cpp | 1189 |
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 ©_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: |