{-# LANGUAGE LambdaCase #-} module Reanimate.Svg ( module Reanimate.Svg , module Reanimate.Svg.Constructors , module Reanimate.Svg.LineCommand , module Reanimate.Svg.BoundingBox , module Reanimate.Svg.Unuse ) where import Control.Lens ((%~), (&), (.~), (^.)) import Control.Monad.State import Graphics.SvgTree hiding (height, line, path, use, width) import Linear.V2 hiding (angle) import Reanimate.Constants import Reanimate.Svg.Constructors import Reanimate.Svg.LineCommand import Reanimate.Svg.BoundingBox import Reanimate.Svg.Unuse import qualified Reanimate.Transform as Transform lowerTransformations :: Tree -> Tree lowerTransformations = worker Transform.identity where updLineCmd m cmd = case cmd of LineMove p -> LineMove $ Transform.transformPoint m p -- LineDraw p -> LineDraw $ Transform.transformPoint m p LineBezier ps -> LineBezier $ map (Transform.transformPoint m) ps LineEnd p -> LineEnd $ Transform.transformPoint m p updPath m = lineToPath . map (updLineCmd m) . toLineCommands worker m t = let m' = m * Transform.mkMatrix (t^.transform) in case t of PathTree path -> PathTree $ path & pathDefinition %~ updPath m' & transform .~ Nothing GroupTree g -> GroupTree $ g & groupChildren %~ map (worker m') & transform .~ Nothing _ -> mkGroup [t] & transform .~ Just [ Transform.toTransformation m ] lowerIds :: Tree -> Tree lowerIds = mapTree worker where worker t@GroupTree{} = t & attrId .~ Nothing worker t@PathTree{} = t & attrId .~ Nothing worker t = t simplify :: Tree -> Tree simplify root = case worker root of [] -> None [x] -> x xs -> mkGroup xs where worker None = [] worker (DefinitionTree d) = concatMap dropNulls $ [DefinitionTree $ d & groupChildren %~ concatMap worker] worker (GroupTree g) | g ^. drawAttributes == defaultSvg = concatMap dropNulls $ concatMap worker (g^.groupChildren) | otherwise = dropNulls $ GroupTree $ g & groupChildren %~ concatMap worker worker t = dropNulls t dropNulls None = [] dropNulls (DefinitionTree d) | null (d^.groupChildren) = [] dropNulls (GroupTree g) | null (g^.groupChildren) = [] dropNulls t = [t] extractPath :: Tree -> [PathCommand] extractPath = worker . simplify . lowerTransformations . pathify where worker (GroupTree g) = concatMap worker (g^.groupChildren) worker (PathTree p) = p^.pathDefinition worker _ = [] withSubglyphs :: [Int] -> (Tree -> Tree) -> Tree -> Tree withSubglyphs target fn = \t -> evalState (worker t) 0 where worker :: Tree -> State Int Tree worker t = case t of GroupTree g -> do cs <- mapM worker (g ^. groupChildren) return $ GroupTree $ g & groupChildren .~ cs PathTree{} -> handleGlyph t CircleTree{} -> handleGlyph t PolyLineTree{} -> handleGlyph t PolygonTree{} -> handleGlyph t EllipseTree{} -> handleGlyph t LineTree{} -> handleGlyph t RectangleTree{} -> handleGlyph t _ -> return t handleGlyph :: Tree -> State Int Tree handleGlyph svg = do n <- get <* modify (+1) if n `elem` target then return $ fn svg else return svg splitGlyphs :: [Int] -> Tree -> (Tree, Tree) splitGlyphs target = \t -> let (_, l, r) = execState (worker id t) (0, [], []) in (mkGroup l, mkGroup r) where handleGlyph :: Tree -> State (Int, [Tree], [Tree]) () handleGlyph t = do (n, l, r) <- get if n `elem` target then put (n+1, l, t:r) else put (n+1, t:l, r) worker :: (Tree -> Tree) -> Tree -> State (Int, [Tree], [Tree]) () worker acc t = case t of GroupTree g -> do let acc' sub = acc (GroupTree $ g & groupChildren .~ [sub]) mapM_ (worker acc') (g ^. groupChildren) PathTree{} -> handleGlyph $ acc t CircleTree{} -> handleGlyph $ acc t PolyLineTree{} -> handleGlyph $ acc t PolygonTree{} -> handleGlyph $ acc t EllipseTree{} -> handleGlyph $ acc t LineTree{} -> handleGlyph $ acc t RectangleTree{} -> handleGlyph $ acc t DefinitionTree{} -> return () _ -> modify $ \(n, l, r) -> (n, acc t:l, r) {- [ (\svg -> svg, ) , (\svg -> svg, )] -} svgGlyphs :: Tree -> [(Tree -> Tree, DrawAttributes, Tree)] svgGlyphs = worker id defaultSvg where worker acc attr = \case GroupTree g -> let acc' sub = acc (GroupTree $ g & groupChildren .~ [sub]) attr' = (g^.drawAttributes) `mappend` attr in concatMap (worker acc' attr') (g ^. groupChildren) t -> [(acc, attr, t)] pathify :: Tree -> Tree pathify = mapTree worker where worker = \case RectangleTree rect | Just (x,y,w,h) <- unpackRect rect -> PathTree $ defaultSvg & drawAttributes .~ rect ^. drawAttributes & strokeLineCap .~ pure CapSquare & pathDefinition .~ [MoveTo OriginAbsolute [V2 x y] ,HorizontalTo OriginRelative [w] ,VerticalTo OriginRelative [h] ,HorizontalTo OriginRelative [-w] ,EndPath ] LineTree line | Just (x1,y1, x2, y2) <- unpackLine line -> PathTree $ defaultSvg & drawAttributes .~ line ^. drawAttributes & pathDefinition .~ [MoveTo OriginAbsolute [V2 x1 y1] ,LineTo OriginAbsolute [V2 x2 y2] ] CircleTree circ | Just (x, y, r) <- unpackCircle circ -> PathTree $ defaultSvg & drawAttributes .~ circ ^. drawAttributes & pathDefinition .~ [MoveTo OriginAbsolute [V2 (x-r) y] ,EllipticalArc OriginRelative [(r, r, 0,True,False,(V2 (r*2) 0)) ,(r, r, 0,True,False,(V2 (-r*2) 0))]] t -> t unpackCircle circ = do let (x,y) = circ ^. circleCenter liftM3 (,,) (unpackNumber x) (unpackNumber y) (unpackNumber $ circ ^. circleRadius) unpackLine line = do let (x1,y1) = line ^. linePoint1 (x2,y2) = line ^. linePoint2 liftM4 (,,,) (unpackNumber x1) (unpackNumber y1) (unpackNumber x2) (unpackNumber y2) unpackRect rect = do let (x', y') = rect ^. rectUpperLeftCorner x <- unpackNumber x' y <- unpackNumber y' w <- unpackNumber =<< rect ^. rectWidth h <- unpackNumber =<< rect ^. rectHeight return (x,y,w,h) unpackNumber n = case toUserUnit defaultDPI n of Num d -> Just d _ -> Nothing