{-# 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.Animation (SVG)
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 False Transform.identity
where
updLineCmd m cmd =
case cmd of
LineMove p -> LineMove $ 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
updPoint m (Num a,Num b) =
case Transform.transformPoint m (V2 a b) of
V2 x y -> (Num x, Num y)
updPoint _ other = other
worker hasPathified 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 hasPathified m')
& transform .~ Nothing
LineTree line ->
LineTree $
line & linePoint1 %~ updPoint m
& linePoint2 %~ updPoint m
ClipPathTree{} -> t
_ | hasPathified ->
mkGroup [t] & transform ?~ [ Transform.toTransformation m ]
_ -> worker True m (pathify t)
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]
removeGroups :: Tree -> [Tree]
removeGroups = worker defaultSvg
where
worker _attr None = []
worker _attr (DefinitionTree d) =
concatMap dropNulls
[DefinitionTree $ d & groupChildren %~ concatMap (worker defaultSvg)]
worker attr (GroupTree g)
| g ^. drawAttributes == defaultSvg =
concatMap dropNulls $
concatMap (worker attr) (g^.groupChildren)
| otherwise =
concatMap (worker (attr <> g ^. drawAttributes)) (g^.groupChildren)
worker attr t = dropNulls (t & drawAttributes .~ attr)
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)
svgGlyphs :: Tree -> [(Tree -> Tree, DrawAttributes, Tree)]
svgGlyphs = worker id defaultSvg
where
worker acc attr =
\case
None -> []
GroupTree g ->
let acc' sub = acc (GroupTree $ g & groupChildren .~ [sub])
attr' = (g^.drawAttributes) `mappend` attr
in concatMap (worker acc' attr') (g ^. groupChildren)
t -> [(acc, (t^.drawAttributes) `mappend` 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)]]
PolyLineTree pl ->
let points = pl ^. polyLinePoints
in PathTree $ defaultSvg
& drawAttributes .~ pl ^. drawAttributes
& pathDefinition .~ pointsToPathCommands points
PolygonTree pg ->
let points = pg ^. polygonPoints
in PathTree $ defaultSvg
& drawAttributes .~ pg ^. drawAttributes
& pathDefinition .~ (pointsToPathCommands points ++ [EndPath])
EllipseTree elip | Just (cx,cy,rx,ry) <- unpackEllipse elip ->
PathTree $ defaultSvg
& drawAttributes .~ elip ^. drawAttributes
& pathDefinition .~
[ MoveTo OriginAbsolute [V2 (cx-rx) cy]
, EllipticalArc OriginRelative [(rx, ry, 0,True,False,V2 (rx*2) 0)
,(rx, ry, 0,True,False,V2 (-rx*2) 0)]]
t -> t
unpackCircle circ = do
let (x,y) = circ ^. circleCenter
liftM3 (,,) (unpackNumber x) (unpackNumber y) (unpackNumber $ circ ^. circleRadius)
unpackEllipse elip = do
let (x,y) = elip ^. ellipseCenter
liftM4 (,,,) (unpackNumber x) (unpackNumber y) (unpackNumber $ elip ^. ellipseXRadius)
(unpackNumber $ elip ^. ellipseYRadius)
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)
pointsToPathCommands points = case points of
[] -> []
(p:ps) -> [ MoveTo OriginAbsolute [p]
, LineTo OriginAbsolute ps ]
unpackNumber n =
case toUserUnit defaultDPI n of
Num d -> Just d
_ -> Nothing
mapSvgPaths :: ([PathCommand] -> [PathCommand]) -> SVG -> SVG
mapSvgPaths fn = mapTree worker
where
worker =
\case
PathTree path -> PathTree $
path & pathDefinition %~ fn
t -> t
mapSvgLines :: ([LineCommand] -> [LineCommand]) -> SVG -> SVG
mapSvgLines fn = mapSvgPaths (lineToPath . fn . toLineCommands)
mapSvgPoints :: (RPoint -> RPoint) -> SVG -> SVG
mapSvgPoints fn = mapSvgLines (map worker)
where
worker (LineMove p) = LineMove (fn p)
worker (LineBezier ps) = LineBezier (map fn ps)
worker (LineEnd p) = LineEnd (fn p)
svgPointsToRadians :: SVG -> SVG
svgPointsToRadians = mapSvgPoints worker
where
worker (V2 x y) = V2 (x/180*pi) (y/180*pi)