packages feed

diagrams-tikz-0.6: src/Diagrams/Backend/TikZ.hs

{-# LANGUAGE TypeFamilies
           , MultiParamTypeClasses
           , FlexibleInstances
           , FlexibleContexts
           , TypeSynonymInstances
           , DeriveDataTypeable
           , ViewPatterns
  #-}
{-|
  A TikZ backend.
  
  Some features are not implemented:
  
  * text alignment (from @Diagrams.TwoD.Text@);
  
  * @clipBy@ (from @Diagrams.TwoD.Path@).
-}

module Diagrams.Backend.TikZ

  ( TikZ(..) -- rendering token
  , Options(..) -- for rendering options specific to TikZ
  , OutputFormat(..) -- output format options
  , absoluteTrail
  ) where

--import qualified Text.LaTeX as L
import qualified Diagrams.Backend.TikZBase as L

import Diagrams.Prelude
import Diagrams.Core.Transform
import Diagrams.TwoD.Shapes
import Diagrams.TwoD.Adjust (adjustDia2D)
import Diagrams.TwoD.Text

import Control.Monad (when)
import Control.Monad.Identity
import Data.Maybe (catMaybes)
--import Data.VectorSpace
import Data.Monoid
import qualified Data.Foldable as F
import Data.Typeable

-- | This data declaration is simply used as a token to distinguish this rendering engine.
data TikZ = TikZ
    deriving Typeable

data OutputFormat
    = Tex
    | TeX { texSize :: SizeSpec2D -- ^ the size of the output is given in cm?
          }
    -- | DVI
    -- | PS  { psSize  :: (Double, Double) -- ^ the size of the output is given in points
    --       }
    -- | PDF { pdfSize :: (Double, Double) -- ^ the size of the output is given in points
    --       }

instance Monoid (Render TikZ R2) where
  mempty  = T $ return ()
  (T r1) `mappend` (T r2) = T (r1 >> r2)

instance Backend TikZ R2 where
--  data Render  TikZ R2 = T (L.LaTeX Identity)
  data Render  TikZ R2 = T (L.Render ())
  type Result  TikZ R2 = IO ()
  data Options TikZ R2 = TikZOptions
      { fileName     :: String       -- ^ the name of the file you want generated
      , outputFormat :: OutputFormat -- ^ the output format and associated options
      }

  withStyle _ s t (T r) = T $ do
    L.rawRenderTikZ "\\begin{scope}\n"
--    tikzTransf t
    tikzStyle s
    r
    tikzStylePost s
    drawOrNot s
    L.stroke
    L.rawRenderTikZ "\\end{scope}\n"

  doRender _ options (T r) =
      let surfaceF surface = L.renderWith surface r
      in  case outputFormat options of
            Tex -> L.withTikZSurface (fileName options) surfaceF
--            TeX (w,h) -> L.withTikZSizeSurface (fileName options) w h surfaceF
            TeX _ -> L.withTikZSurface (fileName options) surfaceF

  adjustDia c opts d = if bypass (outputFormat opts)
                         then (opts,d)
                         else adjustDia2D (getSize . outputFormat) adjustSize c opts d
    where getSize (TeX sz) = sz
          adjustSize sz opts = opts { outputFormat = TeX { texSize = sz } }
          bypass  Tex      = True
          bypass  _        = False

renderT :: (Renderable a TikZ, V a ~ R2) => a -> L.Render ()
renderT a = case (render TikZ a) of T r -> r

tikzStyle :: Style v -> L.Render ()
tikzStyle s = foldr (>>) (return ())
              . catMaybes $ [ handle fColor
                            , handle lColor
                            ]
    where handle :: (AttributeClass a) => (a -> L.Render ()) -> Maybe (L.Render ())
          handle f = f `fmap` getAttr s
          lColor = L.strokeColor . getLineColor
          fColor = L.fillColor . getFillColor

tikzStylePost :: Style v -> L.Render ()
tikzStylePost s = foldr (>>) (return ())
                  . catMaybes $ [ handle lWidth
                                , handle lJoin
                                , handle lCap
                                , handle lDashing
                                , handle fCommand
                                ]
    where handle :: (AttributeClass a) => (a -> L.Render ()) -> Maybe (L.Render ())
          handle f = f `fmap` getAttr s
          fCommand = L.fillCommand . getFillColor
          lWidth = L.lineWidth . getLineWidth
          lCap   = L.lineCap . getLineCap
          lJoin  = L.lineJoin . getLineJoin
          lDashing (getDashing -> Dashing ds offs) =
              L.setDash ds offs

isLineWidthSet :: Style v -> Bool
isLineWidthSet s = case (getAttr s :: Maybe LineWidth) of
                     Nothing -> False
                     Just _  -> True
                         -- where
                         --   getA :: Style v -> Maybe LineWidth
                         --   getA = getAttr

drawOrNot :: Style v -> L.Render ()
drawOrNot s = case (fmap getLineWidth (getAttr s)) of
                Nothing -> L.rawRenderTikZ " [draw]"
                Just x  -> case x < L.epsilon of
                             True  -> L.rawRenderTikZ ""
                             False -> L.rawRenderTikZ " [draw]"

tikzTransf :: T2 -> L.Render ()
tikzTransf t = L.rawRenderTikZ m
    where m = "[cm={" ++ show a1 ++ "," ++ show b1 ++ "," ++ show a2 ++ "," ++ show b2 ++ "," ++ show (c1,c2) ++ "}]\n"
          (a1,a2) = unr2 $ apply t $ r2 (1,0)
          (b1,b2) = unr2 $ apply t $ r2 (0,1)
          (c1,c2) = unr2 $ transl t

instance Renderable (Segment R2) TikZ where
  render _ (Linear v) = T $ L.lineTo v
  render _ (Cubic v1 v2 v3) = T $ L.curveTo v1 v2 v3

instance Renderable (FixedSegment R2) TikZ where
  render _ (FLinear v1 v2) = T $ L.fixedSegment v1 v2
  render _ (FCubic v1 v2 v3 v4) = T $ L.curvedFixedSegment v1 v2 v3 v4

instance Renderable (Trail R2) TikZ where
  render _ (Trail segs c) = T $ do
    mapM_ renderT segs
    when c $ L.closePath

instance Renderable (Path R2) TikZ where
  render _ (Path trs) = T $ L.newPath >> F.mapM_ renderTrail trs
    where renderTrail (p, tr) = do
            let v = p .-. origin
            L.moveTo v
            renderT (absoluteTrail v tr)

absoluteTrail :: R2 -> Trail R2 -> Trail R2
absoluteTrail v (Trail segs c) = Trail (absolute v segs) c

absolute :: R2 -> [Segment R2] -> [Segment R2]
absolute _ [] = []
absolute v (s:ss) = s' : absolute v' ss
  where (v',s') = addV s
        addV (Linear a) = (\p -> (p, Linear p)) (a ^+^ v)
        addV (Cubic a b c) = (c ^+^ v, Cubic (a ^+^ v) (b ^+^ v) (c ^+^ v))

instance Renderable Text TikZ where
  render _ (Text tr _alignment str) = T $ L.rawRenderTikZ $ " \\path " ++ show (unr2 $ transl tr) ++ " node {" ++ str ++ "} "