diagrams-postscript-1.0.1.1: src/Diagrams/Backend/Postscript.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE ViewPatterns #-}
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.Backend.Postscript
-- Copyright : (c) 2013 Diagrams team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- A Postscript rendering backend for diagrams.
--
-- To build diagrams for Postscript rendering use the @Postscript@
-- type in the diagram type construction
--
-- > d :: Diagram Postscript R2
-- > d = ...
--
-- and render giving the @Postscript@ token
--
-- > renderDia Postscript (PostscriptOptions "file.eps" (Width 400) EPS) d
--
-- This IO action will write the specified file.
--
-----------------------------------------------------------------------------
module Diagrams.Backend.Postscript
( -- * Backend token
Postscript(..)
, B
-- * Postscript-specific options
-- $PostscriptOptions
, Options(..), psfileName, psSizeSpec, psOutputFormat
-- * Postscript-supported output formats
, OutputFormat(..)
) where
import qualified Graphics.Rendering.Postscript as C
import Diagrams.Prelude hiding (view)
import Diagrams.TwoD.Adjust (adjustDia2D)
import Diagrams.TwoD.Path (Clip (Clip), getFillRule)
import Diagrams.TwoD.Text
import Diagrams.TwoD.Types
import Control.Lens hiding (transform)
import Control.Monad (when)
import Data.Maybe (catMaybes)
import qualified Data.Foldable as F
import Data.Hashable (Hashable (..))
import qualified Data.List.NonEmpty as N
import Data.Monoid.Split
import Data.Typeable
import GHC.Generics (Generic)
-- | This data declaration is simply used as a token to distinguish this rendering engine.
data Postscript = Postscript
deriving (Eq,Ord,Read,Show,Typeable)
type B = Postscript
-- | Postscript only supports EPS style output at the moment. Future formats would each
-- have their own associated properties that affect the output.
data OutputFormat = EPS -- ^ Encapsulated Postscript output.
deriving (Eq, Ord, Read, Show, Enum, Bounded, Typeable, Generic)
instance Hashable OutputFormat
instance Monoid (Render Postscript R2) where
mempty = C $ return ()
(C x) `mappend` (C y) = C (x >> y)
instance Backend Postscript R2 where
data Render Postscript R2 = C (C.Render ())
type Result Postscript R2 = IO ()
data Options Postscript R2 = PostscriptOptions
{ _psfileName :: String -- ^ the name of the file you want generated
, _psSizeSpec :: SizeSpec2D -- ^ the requested size of the output
, _psOutputFormat :: OutputFormat -- ^ the output format and associated options
}
deriving (Show)
withStyle _ s t (C r) = C $ do
C.save
postscriptMiscStyle s
r
postscriptTransf t
postscriptStyle s
C.stroke
C.restore
doRender _ (PostscriptOptions file size out) (C r) =
let surfaceF surface = C.renderWith surface r
-- Everything except Dims is arbitrary. The backend
-- should have first run 'adjustDia' to update the
-- final size of the diagram with explicit dimensions,
-- so normally we would only expect to get Dims anyway.
(w,h) = sizeFromSpec size
in case out of
EPS -> C.withEPSSurface file (round w) (round h) surfaceF
adjustDia c opts d = adjustDia2D _psSizeSpec setPsSize c opts d
where setPsSize sz o = o { _psSizeSpec = sz }
instance Hashable (Options Postscript R2) where
hashWithSalt s (PostscriptOptions fn sz out) =
s `hashWithSalt` fn
`hashWithSalt` sz
`hashWithSalt` out
sizeFromSpec :: SizeSpec2D -> (Double, Double)
sizeFromSpec size = case size of
Width w' -> (w',w')
Height h' -> (h',h')
Dims w' h' -> (w',h')
Absolute -> (100,100)
psfileName :: Lens' (Options Postscript R2) String
psfileName = lens (\(PostscriptOptions {_psfileName = f}) -> f)
(\o f -> o {_psfileName = f})
psSizeSpec :: Lens' (Options Postscript R2) SizeSpec2D
psSizeSpec = lens (\(PostscriptOptions {_psSizeSpec = s}) -> s)
(\o s -> o {_psSizeSpec = s})
psOutputFormat :: Lens' (Options Postscript R2) OutputFormat
psOutputFormat = lens (\(PostscriptOptions {_psOutputFormat = t}) -> t)
(\o t -> o {_psOutputFormat = t})
instance MultiBackend Postscript R2 where
renderDias b opts ds = doRenderPages b (combineSizes (map fst rs)) (map snd rs) >> return ()
where
mkMax (x,y) = (Max x, Max y)
fromMaxPair (Max x, Max y) = (x,y)
rs = map mkRender ds
mkRender d = (opts', mconcat . map renderOne . prims $ d')
where
(opts', d') = adjustDia b opts d
renderOne (p, (M t, s)) = withStyle b s mempty (render b (transform t p))
renderOne (p, (t1 :| t2, s)) = withStyle b s t1 (render b (transform (t1 <> t2) p))
combineSizes [] = PostscriptOptions "" (Dims 100 100) EPS -- arbitrary
combineSizes (o:os) = o { _psSizeSpec = uncurry Dims . fromMaxPair . sconcat $ f o N.:| fmap f os }
where f = mkMax . sizeFromSpec . _psSizeSpec
doRenderPages _ (PostscriptOptions file size out) pages =
let surfaceF surface = C.renderPagesWith surface (map (\(C r) -> r) pages)
(w,h) = sizeFromSpec size
in case out of
EPS -> C.withEPSSurface file (round w) (round h) surfaceF
renderC :: (Renderable a Postscript, V a ~ R2) => a -> C.Render ()
renderC a = case render Postscript a of C r -> r
-- | Handle \"miscellaneous\" style attributes (clip, font stuff, fill
-- color and fill rule).
postscriptMiscStyle :: Style v -> C.Render ()
postscriptMiscStyle s =
sequence_
. catMaybes $ [ handle clip
, handle fFace
, handle fSlant
, handle fWeight
, handle fSize
, handle fColor
, handle lFillRule
]
where
handle :: AttributeClass a => (a -> C.Render ()) -> Maybe (C.Render ())
handle f = f `fmap` getAttr s
clip = mapM_ (\p -> renderC p >> C.clip) . op Clip
fSize = assign (C.drawState . C.font . C.size) <$> getFontSize
fFace = assign (C.drawState . C.font . C.face) <$> getFont
fSlant = assign (C.drawState . C.font . C.slant) .fromFontSlant <$> getFontSlant
fWeight = assign (C.drawState . C.font . C.weight) . fromFontWeight <$> getFontWeight
fColor c = C.fillColor (getFillColor c)
lFillRule = assign (C.drawState . C.fillRule) . getFillRule
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
postscriptStyle :: Style v -> C.Render ()
postscriptStyle s = sequence_ -- foldr (>>) (return ())
. catMaybes $ [ handle fColor
, handle lColor
, handle lWidth
, handle lJoin
, handle lMiter
, handle lCap
, handle lDashing
]
where handle :: (AttributeClass a) => (a -> C.Render ()) -> Maybe (C.Render ())
handle f = f `fmap` getAttr s
lColor = C.strokeColor . getLineColor
fColor c = C.fillColor (getFillColor c) >> C.fillPreserve
lWidth = C.lineWidth . getLineWidth
lCap = C.lineCap . getLineCap
lJoin = C.lineJoin . getLineJoin
lMiter = C.miterLimit . getLineMiterLimit
lDashing (getDashing -> Dashing ds offs) =
C.setDash ds offs
postscriptTransf :: Transformation R2 -> C.Render ()
postscriptTransf t = C.transform a1 a2 b1 b2 c1 c2
where (R2 a1 a2) = apply t unitX
(R2 b1 b2) = apply t unitY
(R2 c1 c2) = transl t
instance Renderable (Segment Closed R2) Postscript where
render _ (Linear (OffsetClosed (R2 x y))) = C $ C.relLineTo x y
render _ (Cubic (R2 x1 y1)
(R2 x2 y2)
(OffsetClosed (R2 x3 y3)))
= C $ C.relCurveTo x1 y1 x2 y2 x3 y3
instance Renderable (Trail R2) Postscript where
render _ t = flip withLine t $ renderT . lineSegments
where
renderT segs =
C $ do
mapM_ renderC segs
when (isLoop t) C.closePath
-- We need to ignore the fill if we see a line.
-- Ignore fill is part of the drawing state, so
-- it will be cleared by the `restore` after this
-- primitive.
when (isLine t) $ (C.drawState . C.ignoreFill) .= True
instance Renderable (Path R2) Postscript where
render _ p = C $ C.newPath >> F.mapM_ renderTrail (op Path p)
where renderTrail (viewLoc -> (unp2 -> pt, tr)) = do
uncurry C.moveTo pt
renderC tr
instance Renderable Text Postscript where
render _ (Text tr al str) = C $ do
C.save
postscriptTransf tr
case al of
BoxAlignedText xt yt -> C.showTextAlign xt yt str
BaselineText -> C.moveTo 0 0 >> C.showText str
C.restore
-- $PostscriptOptions
--
-- Unfortunately, Haddock does not yet support documentation for
-- associated data families, so we must just provide it manually.
-- This module defines
--
-- > data family Options Postscript R2 = PostscriptOptions
-- > { psfileName :: String -- ^ the name of the file you want generated
-- > , psSizeSpec :: SizeSpec2D -- ^ the requested size of the output
-- > , psOutputFormat :: OutputFormat -- ^ the output format and associated options
-- > }