dia-functions-0.1: Graphics/Diagrams/Escape.hs
-- | Strict evaluation of diagrams with time and size limit
{-# LANGUAGE ScopedTypeVariables #-}
module Graphics.Diagrams.Escape
( escapeDiagram
, numberErrors
) where
import Graphics.Diagrams
import Graphics.Diagrams.Types
import Graphics.Diagrams.Colors (toRGB)
import Data.Data.GenRep.Functions (getErrorIndex)
import Control.Exception.Pure (catchPureErrorsSafe)
import System.SimpleTimeout.Limits
import System.IO.Parallel (twoParallel)
import Control.Monad (liftM2)
import Control.DeepSeq (NFData, deepseq)
import Control.Concurrent.MVar (MVar, newMVar, modifyMVar)
import Control.Monad.State (State, runState)
-------------------------
-- | Strict evaluation of diagrams with time and size limit
escapeDiagram :: TimeLimit -> SizeLimit -> Diagram -> IO Diagram
escapeDiagram t s x = do
ch <- newBudget t s
idv <- newMVar 1 -- to generate identifiers for groups
escapeDiagram' ch idv x
escapeDiagram' :: Budget -> MVar Int -> Diagram -> IO Diagram
escapeDiagram' ch idv = fff where
fff :: Diagram -> IO Diagram
fff x = do
m <- checkBudget ch 1
(return . return . err . timeError)
(return . return $ err "size limit exceeded")
$ fmap (either err id) (catchPureErrorsSafe x) >>= check
m
where
timeError d
= "timeout at " ++ show (round $ 100 * d :: Int) ++ "%"
err s = Error s EmptyDiagram
m1 :: NFData a => (a -> Diagram) -> a -> IO (IO Diagram)
m1 f a = fmap (return . either err f) $ catchPureErrorsSafe $ a `deepseq` a
m2 :: NFData a => (a -> Diagram -> Diagram) -> a -> Diagram -> IO (IO Diagram)
m2 f a b
= fmap (either (\s -> fmap (Error s) $ fff b)
(\a -> fmap (f a) $ fff b))
$ catchPureErrorsSafe $ a `deepseq` a
checkColor :: Color -> Color
checkColor (Color s) = case toRGB s of
Just _ -> Color s
_ -> error $ "Not an SVG color: " ++ s
checkColor (RGB r g b) = RGB (f r) (f g) (f b) where
f x = 0 `max` (1 `min` x)
check :: Diagram -> IO (IO Diagram)
check EmptyDiagram = return $ return EmptyDiagram
check (Circle r) = m1 Circle (if r >= 0 then r else error "negative radius")
check (Text p s) = m1 (uncurry Text) (p, s)
check (Rect a b) = m1 (uncurry Rect) (a, b)
check (Polyline loop l) = fmap g $ m1 (uncurry Polyline) (loop, l) where
g m = m >>= \x -> case x of
Polyline loop l -> do
(b, l) <- decSizeBudget ch $ \x -> case splitAt (x+2) l of
(a,[]) -> (min x (x+2-length a), (True, a))
(a,_) -> (0, (False, a))
return $ if b
then Polyline loop l
else Error "size limit exceeded" $ Polyline False l
_ -> return x
check (Link s x) = m2 Link s x
check (FontFamily s x) = m2 FontFamily s x
check (Move p x) = m2 Move p x
check (Scale t x) = m2 Scale t x
check (ScaleXY x y d) = m2 (uncurry ScaleXY) (x,y) d
check (Rotate t x) = m2 Rotate t x
check (Fill t x) = m2 Fill (checkColor t) x
check (Stroke t x) = m2 Stroke (checkColor t) x
check (StrokeWidth t x) = m2 StrokeWidth (if t >= 0 then t else error "negative stroke width") x
check (Clip a b x) = m2 (uncurry Clip) (a, b) x
check (Error s x) = m2 Error s x
check (TransformMatrix a b c d e f x)
= m2 (\(a,b,c,d,e,f) -> TransformMatrix a b c d e f) (a,b,c,d,e,f) x
check (Pack x f) = return $ do
i <- modifyMVar idv (\i -> return (i+1,i))
fmap (\(x,y)-> Group x i y) $ twoParallel (fff x) (fff (f (Ref i)))
check (Overlay a b) = return $ fmap (uncurry Overlay) $ twoParallel (fff a) (fff b)
check (Ref i) = return $ return $ Ref i
check (Group _ _ _) = error "check: Group not possible"
-- | Error message extraction and numbering
numberErrors
:: Diagram
-> (Diagram, [(String, String)])
numberErrors d = (res, reverse $ map swap errs)
where
swap (a,b) = (b,a)
(res, (_, errs)) = runState (replace d) (0, [])
m2 f x = fmap f $ replace x
replace :: Diagram -> State (Int, [(String, String)]) Diagram
replace (Link s x) = m2 (Link s) x
replace (FontFamily s x) = m2 (FontFamily s) x
replace (Move p x) = m2 (Move p) x
replace (Scale t x) = m2 (Scale t) x
replace (Rotate t x) = m2 (Rotate t) x
replace (Fill t x) = m2 (Fill t) x
replace (Stroke t x) = m2 (Stroke t) x
replace (StrokeWidth t x) = m2 (StrokeWidth t) x
replace (Clip a b x) = m2 (Clip a b) x
replace (TransformMatrix a b c d e f x)
= m2 (TransformMatrix a b c d e f) x
replace (Group a i b) = liftM2 (\a b -> Group a i b) (replace a) (replace b)
replace (Overlay a b) = liftM2 Overlay (replace a) (replace b)
replace (Error e x) = do
i <- getErrorIndex e
x <- replace x
return $ Overlay x (i `textAt` (0,0) `fill` red)
replace x = return x