-- | -- Module : Craftwerk.Core.Driver.Cairo -- Copyright : (c) Malte Harder 2011 -- License : MIT -- Maintainer : Malte Harder -- -- Renders a craftwerk 'Figure' into a 'Cairo.Render' render context. module Graphics.Craftwerk.Core.Driver.Cairo ( -- * Cairo rendering figureToRenderContext )where import Graphics.Craftwerk.Core.Figure import Graphics.Craftwerk.Core.Color import Graphics.Craftwerk.Core.Style import Graphics.Craftwerk.Core.Driver.Generic import qualified Graphics.Rendering.Cairo as Cairo import Graphics.Rendering.Cairo (Matrix) import qualified Graphics.Rendering.Cairo.Matrix as Matrix import Control.Monad import Control.Monad.Reader data Context = Context { styleP :: StyleProperties , strokeMatrix :: Matrix , noDecorations :: Bool } type Render a = ReaderT Context Cairo.Render a -- | Render a Craftwerk 'Figure' within a 'Cairo.Render' context figureToRenderContext :: Figure -> Cairo.Render () figureToRenderContext f = do strokeMatrix <- Cairo.getMatrix runReaderT (figureToRenderContextWithStyle f) Context { styleP = defaultStyle , strokeMatrix = strokeMatrix , noDecorations = False } figureToRenderContextWithStyle Blank = return () figureToRenderContextWithStyle (Style ns a) = local (\c -> c { styleP = mergeProperties (styleP c) ns}) $ figureToRenderContextWithStyle a figureToRenderContextWithStyle (Transform t a) = do lift $ Cairo.save >> case t of Rotate r -> Cairo.rotate (radians r) Scale p -> fnC Cairo.scale p Translate p -> fnC Cairo.translate p figureToRenderContextWithStyle a lift Cairo.restore figureToRenderContextWithStyle (Canvas t a) = local (\c -> c { strokeMatrix = transformationMatrix t (strokeMatrix c) }) $ figureToRenderContextWithStyle (Transform t a) figureToRenderContextWithStyle (Composition a) = mapM_ figureToRenderContextWithStyle a figureToRenderContextWithStyle (NoDecorations a) = local (\c -> c { noDecorations = True }) $ figureToRenderContextWithStyle a figureToRenderContextWithStyle (Path a) = ask >>= \c -> let sp = getProperty $ styleP c in do lift $ do when (sp clip) (do cairoPath a sp Cairo.clip) when (sp fill && not (sp clip)) (do cairoSetColor (sp fillColor) cairoPath a sp Cairo.fill) when (sp stroke && not (sp clip)) (do cairoSetColor (sp lineColor) cairoSetLineJoin (sp lineJoin) cairoSetLineCap (sp lineCap) Cairo.setMiterLimit (sp miterLimit) Cairo.setDash (sp dashes) (sp dashPhase) cairoPath a sp Cairo.setLineWidth (sp lineWidth) Cairo.save Cairo.setMatrix (strokeMatrix c) Cairo.stroke Cairo.restore) -- Avoid running into a deadlock when rendering arrow tips unless (noDecorations c) (figureToRenderContextWithStyle $ arrowTipsForPath a (sp lineWidth) (sp arrowTips)) figureToRenderContextWithStyle (Text a) = lift $ Cairo.textPath a >> Cairo.fill figureToRenderContextWithStyle other = figureToRenderContextWithStyle (genericFigure other) -- Helper functions fnC :: (Double -> Double -> c) -> (Double, Double) -> c fnC = uncurry cairoSetColor color = let rgb = toSRGB color in Cairo.setSourceRGB (channelRed rgb) (channelGreen rgb) (channelBlue rgb) cairoSetLineJoin lj = Cairo.setLineJoin (case lj of JoinRound -> Cairo.LineJoinRound JoinBevel -> Cairo.LineJoinBevel JoinMiter -> Cairo.LineJoinMiter) cairoSetLineCap lc = Cairo.setLineCap (case lc of CapRect -> Cairo.LineCapSquare CapButt -> Cairo.LineCapButt CapRound -> Cairo.LineCapRound) cairoPath a sp = do mapM_ cairoSegment a when (sp closePath) Cairo.closePath cairoSegment (MoveTo p) = fnC Cairo.moveTo p cairoSegment (LineSegment p) = fnC Cairo.lineTo p cairoSegment (ArcSegment (x,y) sa ea r) = if sa > ea then Cairo.arcNegative (x-r*cos(radians sa)) (y-r*sin(radians sa)) r (radians sa) (radians ea) else Cairo.arc (x-r*cos(radians sa)) (y-r*sin(radians sa)) r (radians sa) (radians ea) cairoSegment (CurveSegment (px,py) (c1x,c1y) (c2x,c2y)) = Cairo.curveTo c1x c1y c2x c2y px py transformationMatrix t m = case t of Rotate r -> Matrix.rotate (radians r) m Scale p -> fnC Matrix.scale p m Translate p -> fnC Matrix.translate p m