1.  Introduction

     This document explains the use of the postscript language utility (PS).
PS was developed at the University of Texas at Austin to provide a means for
storing drawings in the Postscript(tm) format.  It is similar to the 
Postscript facility provided by CLIM, except it can accurately calculate
the size of text strings as they appear on a laser-printed page.  If you 
have any comments or suggestions about PS, please send them to me, 
Erik Eilerts, at:  eilerts@cs.utexas.edu

2.  Setup

     The PS code is divided into two files:

          post-init.lisp        -- global variables and such
          postscript.lisp       -- the main code

     The division occured because I load the post-init file first to define
the PS package and then about 20 files later, I get around to loading the
postscript file.

2.1  Output Path

     In post-init.lisp, there is one global variable you need to set and
several other global variables that you may wish to tweak.  
The global variable that you need to set is:

(defvar *default-output-path* "/v/sage/v0/brewery/tree-display.ps")

This is the default file and directory to store the postscript drawing to.
Since I doubt that you use the same file system as we do, you may want to
change this.


2.2  Default Values

     The other global variables determine what the default configuration of
the PS system will be.  The possible values of these variables are documented
in the post-init file.

2.3  Output Device Boundaries

     There is one other default value that deserves mention.  It has to
do with the printing boundries of the laser writer.  Postscript maps the
following coordinate system onto laser writers for a 8.5 x 11 page:

       y

       ^
       |
       |

       0
        0   ---> x  
 
       with (0,0) being in the lower left hand corner

But, not all of these coordinates can appear on the page.  So, the 
laserwriters impose their own clipping boundaries.  I have computed these
bondaries on Apple Laserwriters to be:

                                 Top
                                 784
                        Left 22       592 Right
                                  8
                                Bottom

I think these boundaries will work on other laserwrites.  But, if you
find that your drawings are getting clipped, then you will need to figure
out what the correct values of these coordinates are.


3.  Using PS

     The code for PS is located in the ps package.

     The following code segment shows a typical usage of PS.

    ;; display the configuration menu.  Continue only if the user doesn't abort
    (when (ps:setup-output-configuration ps:*postscripter* *clim-root*)

      ;; open a file to store the drawing in
      (ps:open-stream postscripter associated-window)

      ;; draw some things

      ;; ask the boundary of the drawing and then draw a rectangle
      ;; around this boundary
      (multiple-value-bind (left top right bottom)
                           (ps:compute-bounding-box postscripter)
        (ps:draw-rectangle postscripter
                           (- left 10) (- top 10) (+ right 10) (+ bottom 10)
                           :filled nil))

      ;; close the output file
      (ps:close-stream postscripter))


4.  Reference Manual

     The following sections provide a complete reference to the usage of PS.

4.1  Initialization Functions

  (with-postscript-sizing &rest body)

    Sets the global variable *use-postscript-sizing* inside an
    unwind-protect.  The programmer can then write his own text sizing
    functions that would use *use-postscript-sizing* as a key for
    deciding whether to call CLIM text sizing functions or the
    PS text sizing functions text-size, text-style-ascent,
    text-style-descent, or text-style-height.

  (setup-output-configuration *postscripter* parent)

    Displays a menu on top of the parent window that lets the user choose
    the values of the configuration variables.  Return values:
         nil  -  the user aborted
          t   -  the user chose to continue
     
  (open-stream *postscripter* window)

    Opens a file for storing a postscript drawing.  
      window - a CLIM window.  It is used mainly for color information in
               case the ink is clim:+foreground+ or clim:+background+

  (close-stream *postscripter*)

    Finishes the postscript output and closes the file.


4.2  Configuration Functions

     The following functions return the values of the configuration 
variables.  When used with setf, they can also set the values of these
variables.

  (output-path *postscripter*) 
     - the destination of the postscript drawing.
     :laser-printing      -  it will be sent to a laser printer
     :document-inclusion  -  it will be included in a document, such as LaTeX

  (destination *postscripter*)
     - the destination of the postscript drawing.
     :laser-printing      -  it will be sent to a laser printer
     :document-inclusion  -  it will be included in a document, such as LaTeX

  (orientation *postscripter*)
     - the orientation of the drawing when it's printed
     :portrait   -  no change to coordinates
     :landscape  -  all coordinates are rotated by 90 degrees

  (number-of-pages *postscripter*)
     - whether to dispaly the drawing across multiple pages or not.a
     :one       -  the drawing will be scaled so that it all fits on one page
     :multiple  -  the drawing will be divided up with each part being
                  drawn on a separate page (this option only works when
                  the destination is :laser-printing)

  (display-date *postscripter*)
     -  display the current date on the plot
     t   -  displays the date
     nil -  the date's not displayed

  (plot-label *postscripter*)
     -  A text string to display at the bottom or top of the drawing.  When the
       date is also displayed, the string will appear to the right of the date.
     nil       -  no string to draw
     "string"  -  the string to draw

  (label-location *postscripter*)
     -  location on the page to display the date and plot label
     :top     -  the date/label string starts in the upper left corner
     :bottom  -  the date/label string starts in the bottom left corner

  (label-scale *postscripter*)
     -  the scale at which to draw the date/label string
     > 1 = enlarged
     1.0 = normal size
     < 1 = shrunk

  (multi-page-scale *postscripter*)
     -  the scale of the drawings when number-of-pages is :multiple.
     > 1 = enlarged
     1.0 = normal size
     < 1 = shrunk


4.3  Text functions

    The following text sizing functions return the size of a text string
as it appears on a postscript page.  The font argument is any CLIM font.
    
  (text-style-ascent *postscripter* font)
     -  the ascent of the font

  (text-style-descent *postscripter* font)
     -  the descent of the font

  (text-style-height *postscripter* font)
     -  the height of the font

  (text-size *postscripter* text-string &key text-style (start 0) (end nil))
     -  the width of the string text-string when it has the font text-style.
       start and end can be used to specify a substring within text-string.


4.4  Auxillary Functions

  (set-bounding-box *postscripter* left top right bottom)
     -  sets a fixed bounding box for the postscript drawing.  If this
       function is not used, then the bounding box will be computed
       automatically.

  (compute-bounding-box *postscripter*)
     -  returns the current bounding box of the drawing as:
       (values left top right bottom)

  (start-region *postscripter*)
     -  starts a drawing region.  This serves to localize the effects
       of the translate, scale, and rotate commands.

  (end-region *postscripter*)
     -  ends the current drawing region.  Any translation, rotation, or
       scaling done within the region is discarded.

  (translate *postscripter* dx dy)
     -  translates any further drawings by the dx,dy offset

  (scale *postscripter* sx sy)
     -  scales any further drawings by sx,sy.  The possible values are:
     > 1 = enlarges
     1.0 = no change in size
     < 1 = shrinks

  (rotate *postscripter* degree)
     -  rotates any further drawings by the given degrees

  (set-clipping-box *postscripter* left top right bottom)
     -  sets a clipping box, beyond which the drawing will be clipped.
       Note:  this command starts its own region, since postscript
             only lets you have one clipping box active at one time.
             If this command is called while another clipping box is
             active, then the region for the previous clipping box is
             ended and a new region is started.

  (set-super-clipping-box *postscripter* list-of-points)
     -  sets a clipping region, beyond which the drawing will be clipped.
       list-of-points - a list of (x y) points.
       Note:  the note for set-clipping-box also applies here.

  (end-clipping-box *postscripter*)
     -  ends the current clipping box

  (comment *postscripter* string)
     -  writes string as a comment in the postscript output file.

  (next-page *postscripter*)
     -  this attempts to start a new page for drawing.  This only works
       in the laser-printing mode.  This command doesn't check if all
       regions have been ended before it starts the new page.  This can
       be viewed as either a bug or a feature.

4.4  Drawing

4.4.1 Drawing Keywords
    The following keywords are supported by PS.
                                         
  :ink  
    -  the ink to use when drawing.  The default is *black*.
      Possible values:  
         any clim color
         *black*
         *white*
         [0..1]    - such that 0 = black, 1 = white, and numbers inbetween
                    are converted to grayscale colors

      For now, any non-grayscale clim color will be converted to 
      a gray scale color.
      NOTE:  the reason window is needed in the open-stream call is so
            the window can be used to determine the actual color 
            associated with clim:+foreground+ and clim:+background+

  :line-thickness  
    -  the thickness that a line or arc should have when drawn.
      The default value is 1.
          
  :line-dashes
    -  indicates whether a line or arc should be drawn using a dashed
      or solid line.  
      Possible values:
           NIL   -  use a solid line
            T    -  use an unspecified dash pattern
          array  -  use the dash pattern specified by the array.  The
                   array contains individual components of the dash
                   pattern.  Odd elements specify the length of inked
                   elements, while even elements specify the length of
                   the gaps.  It's an error to have an odd-length array.
      

4.4.2 Drawing Functions

  (draw-point *postscripter* x y  &key :ink :line-thickness)
    -  draws a point at location (x y)

  (draw-line *postscripter* x1 y1 x2 y2 &key :line-thickness :ink :line-dashes)
    -  draws a line from point (x1 y1) to (x2 y2)

  (draw-vector *postscripter* x1 y1 x2 y2  &key :ink :line-thickness
                  (filled t) (arrow-head-length 10) (arrow-base-width 5))
    -  draws a line that starts at (x1 y1) and terminates in an arrow head
      at (x2 y2).  arrow-head-length is the length from the end of the
      line to the arrow head base.  arrow-base-width is the width of the
      arrow head base.  filled is whether the arrow head is filled in or not.

  (draw-lines *postscripter* coord-seq
                             &key :line-thickness :ink :line-dashes)
    -  draws a set of disconnected line segments.  coord-seq is a 
      sequence of pairs (conses) of points.  Each point specifies the
      starting and ending point of a line.  (I don't know if I got this
      right since it seems pretty cryptic and I couldn't find any examples).

      Example:
      (draw-lines *postscripter* '( ((2 . 2) (10 . 10)) ((2 . 10) (10 . 2)) ))

  (draw-arc *postscripter* xc yc xsp ysp deg
                           &key (filled nil) :line-thickness :ink :line-dashes)
    -  draws an arc that starts at the point (xsp ysp), is centered around
      point (xc yc), and is deg degrees long.


  (draw-circle *postscripter* x y rad
                              &key :line-thickness :ink :line-dashes
                                   (filled nil) (start-angle 0) (end-angle 360))
    - draws a circle that's centered at (x y) and has a radius of rad.
     start-angle and end-angle can be used to draw an arc.  Their values are
     specified in degrees.

  (draw-rectangle *postscripter* x1 y1 x2 y2
                        &key (filled t) :line-thickness :ink :line-dashes)
    -  draws an axis-aligned rectangle with (x1 y1) being the upper-left
      corner and (x2 y2) being the lower-right corner.

  (draw-polygon *postscripter* xy-points &key (closed nil) (filled nil) 
                                         :ink :line-thickness :line-dashes)
    -  draws a polygon or sequence of connected lines.  xy-points is
      a linear list of alternating x and y coordinates.  if closed is t, the
      first and last point in xy-points will be connected together.
      If xy-points has an odd length, then an error message will appear.

  (draw-text *postscripter* string x y
                            &key (text-style *default-postscript-font*)
                                 (align-x :left) (align-y :baseline)
                                 :line-thickness :ink)
    -  draws a text string that starts at the point (x y).  text-style is
      the CLIM font to draw the text in.  align-x and align-y specify
      how to draw the text in relation to the start point.  The possible
      values are:

      align-x  (:left :center :right)
      align-y  (:bottom :baseline :center :top)

  (draw-icon *postscripter* icon x y)
    -  draws the icon at (x y), such that (x y) is the upper left corner
      of the icon.


5.  Left Out

    There are currently 2 clim drawing calls that have not been implemented.
  They are:  draw-ellipse and draw-design.  If someone could send me the
  draw-ellipse code, I could probably implement it without too much 
  difficulty.


6.  Conclusion

    PS isn't as seamless as the CLIM Postscript interface.  But, it has the
  advantange that it can correctly determine the size of a text string
  as it is printed on a laser writer.
    For any persons brave enough to try out this system, I wish you luck.


7.  Real Example

;;  Make sure *postscript-afm-directory* is correctly set.

;;  Load post-init.lisp and postscript.lisp.

;;  Load TEST

;;  (test "your.display.name" "default-file-name.ps")

;;  Print your file

(defun test (&optional (display "trueno.cs.utexas.edu")
                       (filename "xxyy.ps"))
  (let* ((clim-root (clim::open-root-window :clx :host display))
         (root-window (clim::open-window-stream :parent clim-root
                                    :scroll-bars nil
                                    :left 20 :top 80
                                    :width 200
                                    :height 200))
         (font (clim:make-text-style :serif :bold :normal))
         (note-icon (clim::make-pattern #2A(
          (1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1)
          (1 1 1 1 1 1 1 1 1 1 1 1 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 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 0 0 0 1 1)
          (1 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 0 0 0 0 1 1)
          (1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 0 0 1 1)
          (1 1 0 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 1)
          (1 1 0 0 1 0 1 0 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1 1 1 0 0 0 1 1)
          (1 1 0 0 1 0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 1)
          (1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 1 0 0 0 0 1 1 1 1 1 0 0 1 1)
          (1 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 0 0 0 0 1 1)
          (1 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 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))
          (list clim:+background+ clim:+foreground+))))

    ;; give a default filename
    (setf (ps:output-path ps:*postscripter*) filename)

    ;; display the configuration menu.  Continue only if the user doesn't abort
    (when (ps:setup-output-configuration ps:*postscripter* clim-root)

      ;; use postscript sizing of text strings
      (ps:with-postscript-sizing

        ;; open a file to store the drawing in
        (ps:open-stream ps:*postscripter* root-window)

        (ps:draw-circle ps:*postscripter* 300 300 200)
        (ps:draw-arc ps:*postscripter* 300 300 400 200 90)

        (ps:draw-vector ps:*postscripter* 200 200 200 300)

        ;; draw a boxed string
        (let* ((string "boxed string")
               (height (ps:text-style-height ps:*postscripter* font))
               (width (ps:text-size ps:*postscripter* string :text-style font))
               (extra-space 10)
               (left (- (- 200 (/ width 2)) extra-space))
               (top (+ 300 extra-space))
               (right (+ left width (* extra-space 2)))
               (bottom (+ top height (* extra-space 2)))
               (center (+ left (/ (- right left) 2))))
          (ps:draw-rectangle ps:*postscripter* left top right bottom
                             :filled nil)
          (ps:draw-text ps:*postscripter* string left top :text-style font
                        :align-x :left :align-y :top)
          (ps:draw-text ps:*postscripter* string right bottom :text-style font
                        :align-x :right :align-y :bottom)
          ;; NOTE:  it's 17 since the icon's width is 34.
          (ps:draw-icon ps:*postscripter* note-icon 
                        (- center 17) (+ bottom 10)))

        ;; demonstrate clipping
        (ps:set-clipping-box ps:*postscripter* 350 200 450 250)
        (ps:draw-line ps:*postscripter* 400 200 400 275)
        (ps:end-clipping-box ps:*postscripter*)

        (ps:set-clipping-box ps:*postscripter* 350 300 450 350)
        (ps:draw-line ps:*postscripter* 400 275 400 350)
        (ps:draw-line ps:*postscripter* 400 275 420 350)
        (ps:draw-line ps:*postscripter* 400 275 380 350)
        (ps:end-clipping-box ps:*postscripter*)

        (ps:draw-text ps:*postscripter* "clipped lines" 400 275
                      :text-style font :align-x :center :align-y :center)

        ;; other wierd stuff
        (ps:draw-polygon ps:*postscripter* '(350 350 450 350 400 420)
                         :closed t :filled nil :line-dashes t)

        (ps:draw-point ps:*postscripter* 380 370 :line-thickness 1)
        (ps:draw-point ps:*postscripter* 400 370 :line-thickness 1)
        (ps:draw-point ps:*postscripter* 420 370 :line-thickness 1)
        (ps:draw-point ps:*postscripter* 400 390 :line-thickness 1)


        ;; ask the boundary of the drawing and then draw a rectangle
        ;; around this boundary
        (multiple-value-bind (left top right bottom)
                             (ps:compute-bounding-box ps:*postscripter*)
          (ps:draw-rectangle ps:*postscripter*
                             (- left 10) (- top 10) (+ right 10) (+ bottom 10)
                             :filled nil))

        ;; close the output file
        (ps:close-stream ps:*postscripter*)))))
 
 