--------------------------------------------------
-- Copyright 1994 by Peter Thiemann
-- $Log: PsOutput.hs,v $
-- Revision 1.1  2004/08/05 11:11:58  malcolm
-- Add a regression testsuite for the nhc98 compiler.  It isn't very good,
-- but it is better than nothing.  I've been using it for about four years
-- on nightly builds, so it's about time it entered the repository!  It
-- includes a slightly altered version of the nofib suite.
-- Instructions are in the README.
--
-- Revision 1.2  1999/01/18 19:38:47  sof
-- Misc (backward compatible) changes to make srcs acceptable
-- to a Haskell 98 compiler.
--
-- Revision 1.1  1996/01/08 20:02:34  partain
-- Initial revision
--
-- Revision 1.4  1994/03/15  15:34:53  thiemann
-- added full color support, XColorDB based
--
--Revision 1.3  1993/08/31  12:31:32  thiemann
--Reflect changes in type FONT
--
--Revision 1.2  1993/08/25  15:11:11  thiemann
--added PostScript prolog to use shorter command names
--fixed backslash bug in psString
--
--Revision 1.1  1993/08/17  12:34:29  thiemann
--Initial revision
--
-- $Locker:  $
--------------------------------------------------
module PsOutput (psShowsWrapper) where

-- import EbnfLayout
import Fonts (FONT, fontName, fontScale, noFont)
import Color (Color (..), showsPsColor, noColor)
import Info (Container (..), GObject (..), TDirection (..), WrapperType (..), INFO(..), ColorInfo(..))

-- psState = (currentColor, currentFont, currentLinewidth)
type PsState = (Color, FONT, Int, ShowS)
type PsTrafo = PsState -> PsState

initialState :: PsState
initialState = (noColor, noFont, -1, id)

setColor :: Color -> PsTrafo
setColor clr st@(clr0, fnt0, lw0, shower)
  | clr == clr0 = st
  | otherwise   = (clr, fnt0, lw0, shower . showsPsColor clr)

setFont :: FONT -> PsTrafo
setFont font st@(clr0, fnt0, lw0, shower)
  | font == fnt0 = st
  | otherwise    = (clr0, font, lw0,
		    shower .
                    showString ('/':fontName font) . showString " findfont " .
                    shows (fontScale font) . showString " scalefont" .
		    showString " setfont\n")
		   

setLineWidth :: Int -> PsTrafo
setLineWidth lw st@(clr0, fnt0, lw0, shower)
  | lw == lw0 = st
  | otherwise = (clr0, fnt0, lw, shower . showsPsNum lw . showString " slw\n")

drawBox :: Bool -> Int -> Int -> Int -> Int -> Int -> PsTrafo
drawBox rounded ax ay width height lw (clr0, fnt0, lw0, shower) = 
	 (clr0, fnt0, lw,
	  shower . showsPsNum ax . showsPsNum ay .
	  showsPsNum width . showsPsNum height . showsPsNum lw .
	  showString (if rounded then " RBox\n" else " Box\n"))

drawString :: Int -> Int -> String -> PsTrafo
drawString ax ay str (clr0, fnt0, lw0, shower) = 
	(clr0, fnt0, lw0, 
	 shower .
	 showsMoveto ax ay .
	 showChar '(' . showString (psString str) . showChar ')' .
	 showString " show\n")

drawRLine :: Int -> Int -> [(Int, Int)] -> PsTrafo
drawRLine ax ay rels (clr0, fnt0, lw0, shower) =
	(clr0, fnt0, lw0,
	 shower .
	 showString "n" .
	 showsMoveto ax ay .
	 foldr (.) (showString " s\n") [ showsRLineto rx ry | (rx, ry) <- rels ])

insertShowS :: ShowS -> PsTrafo
insertShowS shower1 (clr0, fnt0, lw0, shower) = (clr0, fnt0, lw0, shower . shower1)

runTrafo :: PsTrafo -> ShowS
runTrafo f = shower where
		      (_, _, _, shower) = f initialState

psShowsWrapper :: WrapperType
psShowsWrapper title
	 (borderDistX, borderDistY, lineWidth, fatLineWidth, arrowSize, ntFont, tFont,
	  (ntColor, tColor, lineColor, fatLineColor))
		container@(rx, ry, width, height, inOutY, gobj) =
	showString "%!PS-Adobe-1.0\n" .
	showString "%%DocumentFonts: " .
	showString ntFontName . 
	(if ntFontName == tFontName then id else (showChar ' ' . showString tFontName)) .
	showString "\n%%Title: " . showString title .
	showString "\n%%Creator: ebnf2ps (Copyright 1994 by Peter Thiemann)\n" .
	showString "%%Pages: 0\n" .
	showString "%%BoundingBox:" . 
	showsPsNum (psFloor rx) . showsPsNum (psFloor ry) .
	showsPsNum (psCeil (rx+width)) . showsPsNum (psCeil (ry+height)) .
	showString "\n%%EndComments\n" .
	showString psProlog .
	showString "%%EndProlog\n" .
	showString "\n$Ebnf2psBegin\n" .
	runTrafo (psShowsContainer rx ry container) .
	showString "\n$Ebnf2psEnd\n"
	where
  ntFontName = fontName ntFont
  tFontName  = fontName tFont

  psShowsContainer :: Int -> Int -> Container -> PsTrafo
  psShowsContainer ax ay (rx, ry, width, height, inOutY, gobj) =
	case gobj of
	AString color font theString ->
		drawString ax1 ay1 theString .
		setColor color .
		setFont font
	ABox color rounded content ->
		psShowsContainer ax1 ay1 content .
		drawBox rounded ax1 ay1 width height fatLineWidth .
		setColor color
        Arrow color size ->
	    drawRLine  (ax1-size) (ay1+abs size) [(size, -abs size), (-size, -abs size)] .
	    setLineWidth lineWidth .
	    setColor color
	Aline color ->
	    drawRLine ax1 ay1 [(width, height)] .
	    setLineWidth lineWidth .
	    setColor color
	ATurn color dir ->
	    insertShowS(
		showString "n" .
		showsIt dir .
		showString " s\n") .
	    setLineWidth lineWidth .
	    setColor color
		where
		  showsIt SE = showsMoveto ax1 ay1 .
			       showsArcto ax1 (ay1+height) (ax1+width) (ay1+height) radius .
			       showsLineto (ax1+width) (ay1+height)
		  showsIt WN = showsMoveto ax1 ay1 .
			       showsArcto (ax1+width) ay1 (ax1+width) (ay1+height) radius .
			       showsLineto (ax1+width) (ay1+height)
		  showsIt SW = showsMoveto (ax1+width) ay1 .
			       showsArcto (ax1+width) (ay1+height) ax1 (ay1+height) radius .
			       showsLineto ax1 (ay1+height)
		  showsIt NE = showsMoveto (ax1+width) ay1 .
			       showsArcto ax1 ay1 ax1 (ay1+height) radius .
			       showsLineto ax1 (ay1+height)
		  radius = min height width
	AComposite contents ->
		foldr (.) id (map (psShowsContainer ax1 ay1) contents)
    where
      ax1 = ax + rx
      ay1 = ay + ry

-- showsPsColor color =	showString " col" . showsColor color

showsSetlinewidth lineWidth = showsPsNum lineWidth . showString " slw"

showsMoveto x y	=	showsPsXY x y . showString " m"

showsLineto x y =	showsPsXY x y . showString " l"

showsArcto x1 y1 x2 y2 r = showsPsXY x1 y1 . showsPsXY x2 y2 . showsPsNum r .
			   showString " apr\n"

showsRMoveto x y =	showsPsXY x y . showString " rm"

showsRLineto x y =	showsPsXY x y . showString " rl"

showsPsXY x y =		showsPsNum x . showsPsNum y

showsPsNum :: Int -> ShowS
showsPsNum x =		showChar ' ' . shows x100 .
			if x99 == 0 then id
			else showChar '.' . shows x1 . shows x2
			where (x100,x99) = x `divMod` 100
			      (x1,x2) = x99 `divMod` 10

psFloor, psCeil :: Int -> Int
psFloor x = 100 * (x `div` 100)
psCeil  x = 100 * ((x + 99) `div` 100)

-- showsPsInt :: Int -> showS
-- showsPsInt x = showChar ' ' . showInt (x `div` 100)
	
psString "" = ""
psString ('(':cs) = "\\("   ++ psString cs
psString (')':cs) = "\\)"   ++ psString cs
psString ('\\':cs)= "\\\\"  ++ psString cs
psString ('-':cs) = "\\261" ++ psString cs		    -- endash looks much nicer
psString (c:cs)   = c:psString cs

-- Box:		width height linewidth Box -> -
-- draw box at current point

psProlog :: String
psProlog = "\
\/$Ebnf2psDict 100 dict def\n\
\$Ebnf2psDict begin\n\
\/l {lineto} bind def\n\
\/m {moveto} bind def\n\
\/rl {rlineto} bind def\n\
\/rm {rmoveto} bind def\n\
\/s {stroke} bind def\n\
\/n {newpath} bind def\n\
\/gs {gsave} bind def\n\
\/gr {grestore} bind def\n\
\/clp {closepath} bind def\n\
\/slw {setlinewidth} bind def\n\
\/graycol {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul\n\
\4 -2 roll mul setrgbcolor} bind def\n\
\/scol {3 {255 div 3 1 roll} repeat setrgbcolor} bind def\n\
\ \
\/apr {arcto 4 {pop} repeat} def\n\
\/Box {\n\
\  /linewidth exch def\n\
\  linewidth sub /height exch def\n\
\  linewidth sub /width exch def\n\
\ \
\  n m\n\
\  width 0 rl\n\
\  0 height rl\n\
\  width neg 0 rl\n\
\  0 height neg rl\n\
\  clp linewidth slw s\n\
\} def\n\
\ \
\/RBox {\n\
\  /linewidth exch def\n\
\  /height exch def\n\
\  /width exch def\n\
\  /lly exch def\n\
\  /llx exch def\n\
\  linewidth 2 div dup llx add /llx exch def lly add /lly exch def\n\
\  /height height linewidth sub def\n\
\  /width  width  linewidth sub def\n\
\  /height2 height 2 div def\n\
\  /width2  width  2 div def\n\
\  /urx llx width add def\n\
\  /ury lly height add def\n\
\  /mmx llx width2 add def\n\
\  /mmy lly height2 add def\n\
\  /radius width2 height2 ge {height2} {width2} ifelse def\n\
\ \
\  n mmx lly m\n\
\  urx lly urx mmy radius apr\n\
\  urx ury mmx ury radius apr\n\
\  llx ury llx mmy radius apr\n\
\  llx lly mmx lly radius apr\n\
\  mmx lly l\n\
\  clp linewidth slw s\n\
\} def\n\
\end\n\
\/$Ebnf2psBegin {$Ebnf2psDict begin /$Ebnf2psEnteredState save def} def\n\
\/$Ebnf2psEnd {$Ebnf2psEnteredState restore end} def\n\
\\n"
