module Diagrams.Backend.Postscript
(
Postscript(..)
, B
, Options(..), psfileName, psSizeSpec, psOutputFormat
, OutputFormat(..)
, renderDias
) where
import Diagrams.Backend.Postscript.CMYK
import Diagrams.Core.Compile
import qualified Graphics.Rendering.Postscript as C
import Diagrams.Prelude hiding (fillColor, view)
import Diagrams.TwoD.Adjust (adjustDia2D)
import Diagrams.TwoD.Path (Clip (Clip), getFillRule)
import Diagrams.TwoD.Text
import Control.Lens hiding (transform)
import Control.Monad (when)
import qualified Control.Monad.StateStack as SS
import Control.Monad.Trans (lift)
import Data.Maybe (catMaybes, isJust)
import qualified Data.Foldable as F
import Data.Hashable (Hashable (..))
import Data.Tree
import Data.Typeable
import GHC.Generics (Generic)
data Postscript = Postscript
deriving (Eq,Ord,Read,Show,Typeable)
type B = Postscript
type instance V Postscript = V2
type instance N Postscript = Double
data OutputFormat = EPS
deriving (Eq, Ord, Read, Show, Enum, Bounded, Typeable, Generic)
instance Hashable OutputFormat
data PostscriptState
= PostscriptState { _accumStyle :: Style V2 Double
, _ignoreFill :: Bool
}
$(makeLenses ''PostscriptState)
instance Default PostscriptState where
def = PostscriptState
{ _accumStyle = mempty
, _ignoreFill = False
}
type RenderM a = SS.StateStackT PostscriptState C.Render a
liftC :: C.Render a -> RenderM a
liftC = lift
runRenderM :: RenderM a -> C.Render a
runRenderM = flip SS.evalStateStackT def
save :: RenderM ()
save = SS.save >> liftC C.save
restore :: RenderM ()
restore = liftC C.restore >> SS.restore
instance Monoid (Render Postscript V2 Double) where
mempty = C $ return ()
(C x) `mappend` (C y) = C (x >> y)
instance Backend Postscript V2 Double where
data Render Postscript V2 Double = C (RenderM ())
type Result Postscript V2 Double = IO ()
data Options Postscript V2 Double = PostscriptOptions
{ _psfileName :: String
, _psSizeSpec :: SizeSpec V2 Double
, _psOutputFormat :: OutputFormat
}
deriving (Show)
renderRTree _ opts t =
let surfaceF surface = C.renderWith surface r
V2 w h = specToSize 100 (opts^.psSizeSpec)
r = runRenderM . runC . toRender $ t
in case opts^.psOutputFormat of
EPS -> C.withEPSSurface (opts^.psfileName) (round w) (round h) surfaceF
adjustDia = adjustDia2D psSizeSpec
runC :: Render Postscript V2 Double -> RenderM ()
runC (C r) = r
getStyleAttrib :: AttributeClass a => (a -> b) -> RenderM (Maybe b)
getStyleAttrib f = (fmap f . getAttr) <$> use accumStyle
toRender :: RTree Postscript V2 Double a -> Render Postscript V2 Double
toRender (Node (RPrim p) _) = render Postscript p
toRender (Node (RStyle sty) rs) = C $ do
save
postscriptStyle sty
accumStyle %= (<> sty)
runC $ F.foldMap toRender rs
restore
toRender (Node _ rs) = F.foldMap toRender rs
instance Hashable (Options Postscript V2 Double) where
hashWithSalt s (PostscriptOptions fn sz out) =
s `hashWithSalt` fn
`hashWithSalt` sz
`hashWithSalt` out
psfileName :: Lens' (Options Postscript V2 Double) String
psfileName = lens (\(PostscriptOptions {_psfileName = f}) -> f)
(\o f -> o {_psfileName = f})
psSizeSpec :: Lens' (Options Postscript V2 Double) (SizeSpec V2 Double)
psSizeSpec = lens (\(PostscriptOptions {_psSizeSpec = s}) -> s)
(\o s -> o {_psSizeSpec = s})
psOutputFormat :: Lens' (Options Postscript V2 Double) OutputFormat
psOutputFormat = lens (\(PostscriptOptions {_psOutputFormat = t}) -> t)
(\o t -> o {_psOutputFormat = t})
renderDias :: (Semigroup m, Monoid m) =>
Options Postscript V2 Double -> [QDiagram Postscript V2 Double m] -> IO [()]
renderDias opts ds = case opts^.psOutputFormat of
EPS -> C.withEPSSurface (opts^.psfileName) (round w) (round h) surfaceF
where
surfaceF surface = C.renderPagesWith surface rs
dropMid (x, _, z) = (x,z)
optsdss = map (dropMid . adjustDia Postscript opts) ds
g2o = scaling (sqrt (w * h))
rs = map (runRenderM . runC . toRender . toRTree g2o . snd) optsdss
sizes = map (specToSize 1 . view psSizeSpec . fst) optsdss
V2 w h = foldBy (liftA2 max) zero sizes
renderC :: (Renderable a Postscript, V a ~ V2, N a ~ Double) => a -> RenderM ()
renderC = runC . render Postscript
postscriptStyle :: Style v Double -> RenderM ()
postscriptStyle s =
sequence_
. catMaybes $ [ handle clip
, handle lFillRule
, handle lWidth
, handle lJoin
, handle lMiter
, handle lCap
, handle lDashing
]
where
handle :: AttributeClass a => (a -> RenderM ()) -> Maybe (RenderM ())
handle f = f `fmap` getAttr s
clip = mapM_ (\p -> postscriptPath p >> liftC C.clip) . op Clip
lFillRule = liftC . assign (C.drawState . C.fillRule) . getFillRule
lWidth = liftC . C.lineWidth . getLineWidth
lCap = liftC . C.lineCap . getLineCap
lJoin = liftC . C.lineJoin . getLineJoin
lMiter = liftC . C.miterLimit . getLineMiterLimit
lDashing (getDashing -> Dashing ds offs) = liftC $ C.setDash ds offs
fromFontSlant :: FontSlant -> C.FontSlant
fromFontSlant FontSlantNormal = C.FontSlantNormal
fromFontSlant FontSlantItalic = C.FontSlantItalic
fromFontSlant FontSlantOblique = C.FontSlantOblique
fromFontWeight :: FontWeight -> C.FontWeight
fromFontWeight FontWeightNormal = C.FontWeightNormal
fromFontWeight FontWeightBold = C.FontWeightBold
fromFontWeight _ = C.FontWeightNormal
postscriptTransf :: Transformation V2 Double -> C.Render ()
postscriptTransf t = C.transform a1 a2 b1 b2 c1 c2
where (V2 a1 a2) = apply t unitX
(V2 b1 b2) = apply t unitY
(V2 c1 c2) = transl t
instance Renderable (Segment Closed V2 Double) Postscript where
render _ (Linear (OffsetClosed v)) = C . liftC $ uncurry C.relLineTo (unr2 v)
render _ (Cubic (unr2 -> (x1, y1))
(unr2 -> (x2, y2))
(OffsetClosed (unr2 -> (x3, y3))))
= C . liftC $ C.relCurveTo x1 y1 x2 y2 x3 y3
instance Renderable (Trail V2 Double) Postscript where
render _ = withTrail renderLine renderLoop
where
renderLine ln = C $ do
mapM_ renderC (lineSegments ln)
ignoreFill .= True
renderLoop lp = C $ do
case loopSegments lp of
(segs, Linear _) -> mapM_ renderC segs
_ -> mapM_ renderC (lineSegments . cutLoop $ lp)
liftC C.closePath
instance Renderable (Path V2 Double) Postscript where
render _ p = C $ do
postscriptPath p
f <- getStyleAttrib getFillTexture
s <- getStyleAttrib getLineTexture
fk <- getStyleAttrib getFillColorCMYK
sk <- getStyleAttrib getLineColorCMYK
ign <- use ignoreFill
setFillColor f fk
when ((isJust f || isJust fk) && not ign) $ liftC C.fillPreserve
setStrokeColor s sk
liftC C.stroke
setFillColor :: Maybe (Texture Double) -> Maybe (CMYK) -> RenderM ()
setFillColor c cmyk = do
liftC $ maybe (return ()) C.fillColor c
liftC $ maybe (return ()) C.fillColorCMYK cmyk
setStrokeColor :: Maybe (Texture Double) -> Maybe (CMYK) -> RenderM ()
setStrokeColor c cmyk = do
liftC $ maybe (return ()) C.strokeColor c
liftC $ maybe (return ()) C.strokeColorCMYK cmyk
postscriptPath :: Path V2 Double -> RenderM ()
postscriptPath (Path trs) = do
liftC C.newPath
ignoreFill .= False
F.mapM_ renderTrail trs
where renderTrail (viewLoc -> (unp2 -> pt, tr)) = do
liftC $ uncurry C.moveTo pt
renderC tr
if' :: Monad m => (a -> m ()) -> Maybe a -> m ()
if' = maybe (return ())
instance Renderable (Text Double) Postscript where
render _ (Text tr al str) = C $ do
ff <- getStyleAttrib getFont
fs <- getStyleAttrib (fromFontSlant . getFontSlant)
fw <- getStyleAttrib (fromFontWeight . getFontWeight)
size' <- getStyleAttrib getFontSize
f <- getStyleAttrib getFillTexture
fk <- getStyleAttrib getFillColorCMYK
save
setFillColor f fk
liftC $ do
if' (assign (C.drawState . C.font . C.size)) size'
if' (assign (C.drawState . C.font . C.face)) ff
if' (assign (C.drawState . C.font . C.slant)) fs
if' (assign (C.drawState . C.font . C.weight)) fw
when (isJust f || isJust fk) $ liftC C.fillPreserve
liftC $ postscriptTransf tr
case al of
BoxAlignedText xt yt -> liftC $ C.showTextAlign xt yt str
BaselineText -> liftC $ C.moveTo 0 0 >> C.showText str
restore