diagrams-postscript-0.7: src/Diagrams/Backend/Postscript.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# 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(..)
-- * Postscript-specific options
-- $PostscriptOptions
, Options(..)
-- * Postscript-supported output formats
, OutputFormat(..)
) where
import qualified Graphics.Rendering.Postscript as C
import Diagrams.Prelude
import Diagrams.Core.Transform
import Diagrams.TwoD.Adjust (adjustDia2D)
import Diagrams.TwoD.Ellipse
import Diagrams.TwoD.Path (Clip (..), getFillRule)
import Diagrams.TwoD.Shapes
import Diagrams.TwoD.Size (requiredScaleT)
import Diagrams.TwoD.Text
import Control.Applicative ((<$>))
import Control.Monad (when)
import Data.Maybe (catMaybes, fromMaybe)
import Data.VectorSpace
import qualified Data.Foldable as F
import qualified Data.List.NonEmpty as N
import Data.Monoid hiding ((<>))
import Data.Monoid.MList
import Data.Monoid.Split
import Data.Typeable
-- | This data declaration is simply used as a token to distinguish this rendering engine.
data Postscript = Postscript
deriving (Eq,Ord,Read,Show,Typeable)
-- | 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)
instance Monoid (Render Postscript R2) where
mempty = C $ return ()
(C r1) `mappend` (C r2) = C (r1 >> r2)
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 }
sizeFromSpec size = case size of
Width w' -> (w',w')
Height h' -> (h',h')
Dims w' h' -> (w',h')
Absolute -> (100,100)
instance MultiBackend Postscript R2 where
renderDias b opts ds = doRenderPages b (combineSizes (map fst rs)) (map snd rs) >> return ()
where
mkMax (a,b) = (Max a, Max b)
fromMaxPair (Max a, Max b) = (a,b)
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 (o:os) = o { psSizeSpec = uncurry Dims . fromMaxPair . sconcat $ f o N.:| fmap f os }
where f = mkMax . sizeFromSpec . psSizeSpec
doRenderPages _ (PostscriptOptions file size out) rs =
let surfaceF surface = C.renderPagesWith surface (map (\(C r) -> r) rs)
(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) . getClip
fSize = C.setFontSize <$> getFontSize
fFace = C.setFontFace <$> getFont
fSlant = C.setFontSlant . fromFontSlant <$> getFontSlant
fWeight = C.setFontWeight . fromFontWeight <$> getFontWeight
fColor c = C.fillColor (getFillColor c)
lFillRule = C.setFillRule . 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 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
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 (a1,a2) = unr2 $ apply t unitX
(b1,b2) = unr2 $ apply t unitY
(c1,c2) = unr2 $ transl t
instance Renderable (Segment Closed R2) Postscript where
render _ (Linear (OffsetClosed (unr2 -> v))) = C $ uncurry C.relLineTo v
render _ (Cubic (unr2 -> (x1,y1))
(unr2 -> (x2,y2))
(OffsetClosed (unr2 -> (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
instance Renderable (Path R2) Postscript where
render _ (Path trs) = C $ C.newPath >> F.mapM_ renderTrail trs
where renderTrail (viewLoc -> (unp2 -> p, tr)) = do
uncurry C.moveTo p
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
-- > }