diagrams-tikz-0.1.1.0: src/Diagrams/Backend/TikZBase.hs
{-# LANGUAGE GeneralizedNewtypeDeriving,ViewPatterns #-}
--module Graphics.Rendering.Canvas
module Diagrams.Backend.TikZBase
( Render(..)
, renderWith
, withTikZSurface
, renderTikZ
, rawRenderTikZ
, fixedSegment
, curvedFixedSegment
-- , withHTMLSurface
, newPath
, moveTo
, lineTo
, curveTo
-- , arc
, closePath
, stroke
-- , fill
-- , transform
-- , save
-- , restore
-- , translate
-- , scale
-- , rotate
, setDash
, fillColor
, fillCommand
, drawCommand
, strokeColor
, lineWidth
, lineCap
, lineJoin
, epsilon
) where
import Diagrams.Attributes(Color(..),LineCap(..),LineJoin(..))
import Diagrams.TwoD.Types
import Control.Monad.Writer
import Data.List(intersperse)
import Data.DList(DList,toList,fromList)
import Data.Word(Word8)
import System.IO (openFile, hPutStr, IOMode(..), hClose)
newtype Render m = Render { runRender :: WriterT (DList String) IO m }
deriving (Functor, Monad, MonadWriter (DList String))
data Surface = Surface { header :: String, footer :: String, width :: Int, height :: Int, fileName :: String }
renderWith :: MonadIO m => Surface -> Render a -> m a
renderWith s r = liftIO $ do (v,ss) <- runWriterT $ (runRender r)
h <- openFile (fileName s) WriteMode
hPutStr h (header s)
mapM_ (hPutStr h) (toList ss)
hPutStr h (footer s)
hClose h
return v
withTikZSurface :: String -> (Surface -> IO a) -> IO a
withTikZSurface file f = f s
where s = Surface tikzHeader tikzFooter 0 0 file
{-
withTikZSizeSurface :: String -> Double -> Double -> (Surface -> IO a) -> IO a
withTikZSizeSurface file w h f = f s
where s = Surface (tikzSizeHeader w h) (tikzSizeFooter w h)
-}
{-
withHTMLSurface :: String -> Int -> Int -> (Surface -> IO a) -> IO a
withHTMLSurface file w h f = f s
where s = Surface htmlHeader (htmlFooter w h) w h file
-}
rawRenderTikZ :: String -> Render ()
rawRenderTikZ s = tell $ fromList [s]
renderTikZ :: String -> Render ()
renderTikZ s = tell $ fromList [texPrefix, s, ";\n"]
newPath :: Render ()
newPath = rawRenderTikZ "\\path "
closePath :: Render ()
closePath = rawRenderTikZ " -- cycle "
moveTo :: R2 -> Render ()
moveTo v = rawRenderTikZ $ " " ++ showR2 v ++ " "
lineTo :: R2 -> Render ()
lineTo v = rawRenderTikZ $ " -- " ++ showR2 v ++ " "
curveTo :: R2 -> R2 -> R2 -> Render ()
curveTo v2 v3 v4 = rawRenderTikZ $ " .. controls " ++ showR2 v2 ++ " and " ++ showR2 v3 ++ " .. " ++ showR2 v4 ++ " "
stroke :: Render ()
stroke = rawRenderTikZ "; "
showColorTikZ :: (Color c) => c -> String
showColorTikZ c = showD4 r ++ "," ++ showD4 g ++ "," ++ showD4 b
where (r,g,b,_a) = colorToRGBA c
-- setColor :: (Color c) => c -> Render ()
-- setColor c = rawRenderTikZ $ " \\color[rgb]{" ++ showColorTikZ c ++ "} "
fillColor :: (Color c) => c -> Render ()
fillColor c = rawRenderTikZ $ " \\definecolor{fc}{rgb}{" ++ showColorTikZ c ++ "} \\pgfsetfillcolor{fc}"
fillCommand :: (Color c) => c -> Render ()
fillCommand _c = rawRenderTikZ $ " [fill] "
drawCommand :: Double -> Render ()
drawCommand w
| w < epsilon = rawRenderTikZ ""
| otherwise = rawRenderTikZ " [draw]"
strokeColor :: (Color c) => c -> Render ()
strokeColor c = rawRenderTikZ $ " \\definecolor{sc}{rgb}{" ++ showColorTikZ c ++ "} \\pgfsetstrokecolor{sc}"
-- about 28.5pt = 1cm. Get the official value.
numPtsPerCm :: Double
numPtsPerCm = 28.5
-- a number (line width) below this is regarded as zero
epsilon :: Double
epsilon = 0.001
convertCmToPt :: Double -> Double
convertCmToPt x = numPtsPerCm * x
lineWidth :: Double -> Render ()
lineWidth w = rawRenderTikZ $
"[line width=" ++ showD4 (convertCmToPt w) ++ "] "
setDash :: [Double] -> Double -> Render ()
setDash ds offs
= rawRenderTikZ $ "[dash pattern="
++ concat (zipWith (++) (cycle [" on "," off "]) (map (show . convertCmToPt) ds))
++ "] [dash phase=" ++ show (convertCmToPt offs)
++ "] "
lineCap :: LineCap -> Render ()
lineCap lc = rawRenderTikZ $
"[line cap=" ++ fromLineCap lc ++ "] "
lineJoin :: LineJoin -> Render ()
lineJoin lj = rawRenderTikZ $
"[line join=" ++ fromLineJoin lj ++ "] "
fromLineCap :: LineCap -> String
fromLineCap LineCapRound = show "round"
fromLineCap LineCapSquare = show "rect"
fromLineCap _ = show "butt"
fromLineJoin :: LineJoin -> String
fromLineJoin LineJoinRound = show "round"
fromLineJoin LineJoinBevel = show "bevel"
fromLineJoin _ = show "miter"
fixedSegment :: P2 -> P2 -> Render ()
fixedSegment v1 v2 = rawRenderTikZ $ " " ++ showP2 v1 ++ " -- " ++ showP2 v2 ++ ";\n"
curvedFixedSegment :: P2 -> P2 -> P2 -> P2 -> Render ()
curvedFixedSegment v1 v2 v3 v4 = rawRenderTikZ $ "\\draw " ++ showP2 v1 ++ " .. controls " ++ showP2 v2 ++ " and " ++ showP2 v3 ++ " .. " ++ showP2 v4 ++ ";\n"
{-
arc :: Double -> Double -> Double -> Double -> Double -> Render ()
arc a b c d e = mkJSCall "arcTo" [a,b,c,d,e]
fill :: Render ()
fill = renderJS "fill()"
transform :: Double -> Double -> Double -> Double -> Double -> Double -> Render ()
transform ax ay bx by tx ty =
if vs /= [1.0,0.0,0.0,1.0,0.0,0.0]
then mkJSCall "transform" vs
else return ()
where vs = [ax,ay,bx,by,tx,ty]
save :: Render ()
save = renderJS "save()"
restore :: Render ()
restore = renderJS "restore()"
translate :: Double -> Double -> Render ()
translate x y = mkJSCall "translate" [x,y]
scale :: Double -> Double -> Render ()
scale x y = mkJSCall "scale" [x,y]
rotate :: Double -> Render ()
rotate t = mkJSCall "rotate" [t]
-- TODO: Instead of always filling and stroking keep state
-- in Render that knows if it needs to fill or stroke.
jsHeader = " function renderDiagram(c) {\n"
++ jsPrefix ++ "fillStyle = \"rgba(0,0,0,0.0)\";\n"
++ jsPrefix ++ "strokeStyle = \"rgba(0,0,0,1.0)\";\n"
++ jsPrefix ++ "miterLimit = 10;\n"
jsFooter = " }\n"
-}
texPrefix = "\\"
tikzHeader = "\\begin{tikzpicture}\n"
tikzFooter = "\\end{tikzpicture}\n"
showD :: Int -> Double -> String
showD n x = show $ fromIntegral (round (x * 10.0^n)) / 10.0^n
showD4 :: Double -> String
showD4 = showD 4
showR2 :: R2 -> String
showR2 (unr2 -> (x,y)) = "(" ++ showD4 x ++ "," ++ showD4 y ++ ")"
showP2 :: P2 -> String
showP2 (unp2 -> (x,y)) = "(" ++ showD4 x ++ "," ++ showD4 y ++ ")"