never executed always true always false
    1 {-| Functions for creating basic SVG elements and applying transformations to them. -}
    2 module Reanimate.Svg.Constructors
    3   ( -- * Primitive shapes
    4     mkCircle
    5   , mkEllipse
    6   , mkRect
    7   , mkLine
    8   , mkPath
    9   , mkPathString
   10   , mkPathText
   11   , mkLinePath
   12   , mkLinePathClosed
   13   , mkClipPath
   14   , mkText
   15   -- * Grouping shapes and definitions
   16   , mkGroup
   17   , mkDefinitions
   18   , mkUse
   19   -- * Attributes
   20   , withId
   21   , withStrokeColor
   22   , withStrokeColorPixel
   23   , withStrokeDashArray
   24   , withStrokeLineJoin
   25   , withFillColor
   26   , withFillColorPixel
   27   , withFillOpacity
   28   , withGroupOpacity
   29   , withStrokeWidth
   30   , withClipPathRef
   31   -- * Transformations
   32   , center
   33   , centerX
   34   , centerY
   35   , centerUsing
   36   , translate
   37   , rotate
   38   , rotateAroundCenter
   39   , rotateAround
   40   , scale
   41   , scaleToSize
   42   , scaleToWidth
   43   , scaleToHeight
   44   , scaleXY
   45   , flipXAxis
   46   , flipYAxis
   47   , aroundCenter
   48   , aroundCenterX
   49   , aroundCenterY
   50   , withTransformations
   51   , withViewBox
   52   -- * Other
   53   , mkColor
   54   , mkBackground
   55   , mkBackgroundPixel
   56   , gridLayout
   57 
   58   ) where
   59 
   60 import           Codec.Picture                (PixelRGBA8 (..))
   61 import           Control.Lens                 ((&), (.~), (?~))
   62 import           Data.Attoparsec.Text         (parseOnly)
   63 import qualified Data.Map                     as Map
   64 import qualified Data.Text                    as T
   65 import           Graphics.SvgTree             hiding (height, line, path, use,
   66                                                width)
   67 import           Graphics.SvgTree.NamedColors
   68 import           Graphics.SvgTree.PathParser
   69 import           Linear.V2                    hiding (angle)
   70 import           Reanimate.Constants
   71 import           Reanimate.Svg.BoundingBox
   72 
   73 -- | Apply list of transformations to given image.
   74 withTransformations :: [Transformation] -> Tree -> Tree
   75 withTransformations transformations t =
   76   mkGroup [t] & transform ?~ transformations
   77 
   78 -- | @translate x y image@ moves the @image@ by @x@ along X-axis and by @y@ along Y-axis.
   79 translate :: Double -> Double -> Tree -> Tree
   80 translate x y = withTransformations [Translate x y]
   81 
   82 -- | @rotate angle image@ rotates the @image@ around origin @(0,0)@ counterclockwise by @angle@
   83 --   given in degrees.
   84 rotate :: Double -> Tree -> Tree
   85 rotate a = withTransformations [Rotate a Nothing]
   86 
   87 -- | @rotate angle point image@ rotates the @image@ around given @point@ counterclockwise by
   88 --   @angle@ given in degrees.
   89 rotateAround :: Double -> RPoint -> Tree -> Tree
   90 rotateAround a (V2 x y) = withTransformations [Rotate a (Just (x,y))]
   91 
   92 -- | @rotate angle image@ rotates the @image@ around the center of its bounding box counterclockwise
   93 --   by @angle@ given in degrees.
   94 rotateAroundCenter :: Double -> Tree -> Tree
   95 rotateAroundCenter a t =
   96     rotateAround a (V2 (x+w/2) (y+h/2)) t
   97   where
   98     (x,y,w,h) = boundingBox t
   99 
  100 -- | @aroundCenter f image@ first moves the image so the center of its bounding box is at the origin
  101 --   @(0, 0)@, applies transformation @f@ to it and then moves the transformed image back to its
  102 --   original position.
  103 aroundCenter :: (Tree -> Tree) -> Tree -> Tree
  104 aroundCenter fn t =
  105     translate (-offsetX) (-offsetY) $ fn $ translate offsetX offsetY t
  106   where
  107     offsetX = -x-w/2
  108     offsetY = -y-h/2
  109     (x,y,w,h) = boundingBox t
  110 
  111 aroundCenterY :: (Tree -> Tree) -> Tree -> Tree
  112 aroundCenterY fn t =
  113     translate 0 (-offsetY) $ fn $ translate 0 offsetY t
  114   where
  115     offsetY = -y-h/2
  116     (_x,y,_w,h) = boundingBox t
  117 
  118 aroundCenterX :: (Tree -> Tree) -> Tree -> Tree
  119 aroundCenterX fn t =
  120     translate (-offsetX) 0 $ fn $ translate offsetX 0 t
  121   where
  122     offsetX = -x-w/2
  123     (x,_y,w,_h) = boundingBox t
  124 
  125 -- | Scale the image uniformly by given factor along both X and Y axes.
  126 -- For example @scale 2 image@  makes the image twice as large, while @scale 0.5 image@ makes it
  127 -- half the original size. Negative values are also allowed, and lead to flipping the image along
  128 -- both X and Y axes.
  129 scale :: Double -> Tree -> Tree
  130 scale a = withTransformations [Scale a Nothing]
  131 
  132 -- | @scaleToSize width height@ resizes the image so that its bounding box has corresponding @width@
  133 --   and @height@.
  134 scaleToSize :: Double -> Double -> Tree -> Tree
  135 scaleToSize w h t =
  136     scaleXY (w/w') (h/h') t
  137   where
  138     (_x, _y, w', h') = boundingBox t
  139 
  140 -- | @scaleToWidth width@ scales the image so that the width of its bounding box ends up having
  141 --   given @width@.
  142 scaleToWidth :: Double -> Tree -> Tree
  143 scaleToWidth w t =
  144     scale (w/w') t
  145   where
  146     (_x, _y, w', _h') = boundingBox t
  147 
  148 -- | @scaleToHeight height@ scales the image so that the height of its bounding box ends up having
  149 --   given @height@.
  150 scaleToHeight :: Double -> Tree -> Tree
  151 scaleToHeight h t =
  152     scale (h/h') t
  153   where
  154     (_x, _y, _w', h') = boundingBox t
  155 
  156 -- | Similar to 'scale', except scale factors for X and Y axes are specified separately.
  157 scaleXY :: Double -> Double -> Tree -> Tree
  158 scaleXY x y = withTransformations [Scale x (Just y)]
  159 
  160 
  161 -- | Flip the image along vertical axis so that what was on the right will end up on left and vice
  162 --   versa.
  163 flipXAxis :: Tree -> Tree
  164 flipXAxis = scaleXY (-1) 1
  165 
  166 -- | Flip the image along horizontal so that what was on the top will end up in the bottom and vice
  167 --   versa.
  168 flipYAxis :: Tree -> Tree
  169 flipYAxis = scaleXY 1 (-1)
  170 
  171 -- | Translate given image so that the center of its bouding box coincides with coordinates
  172 --   @(0, 0)@.
  173 center :: Tree -> Tree
  174 center t = centerUsing t t
  175 
  176 -- | Translate given image so that the X-coordinate of the center of its bouding box is 0.
  177 centerX :: Tree -> Tree
  178 centerX t = translate (-x-w/2) 0 t
  179   where
  180     (x, _y, w, _h) = boundingBox t
  181 
  182 -- | Translate given image so that the Y-coordinate of the center of its bouding box is 0.
  183 centerY :: Tree -> Tree
  184 centerY t = translate 0 (-y-h/2) t
  185   where
  186     (_x, y, _w, h) = boundingBox t
  187 
  188 centerUsing :: Tree -> Tree -> Tree
  189 centerUsing a = translate (-x-w/2) (-y-h/2)
  190   where
  191     (x, y, w, h) = boundingBox a
  192 
  193 -- | Create 'Texture' based on SVG color name.
  194 --   See <https://en.wikipedia.org/wiki/Web_colors#X11_color_names> for the list of available names.
  195 --   If the provided name doesn't correspond to valid SVG color name, white-ish color is used.
  196 mkColor :: String -> Texture
  197 mkColor name =
  198   case Map.lookup (T.pack name) svgNamedColors of
  199     Nothing -> ColorRef (PixelRGBA8 240 248 255 255)
  200     Just c  -> ColorRef c
  201 
  202 -- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke>
  203 withStrokeColor :: String -> Tree -> Tree
  204 withStrokeColor color = strokeColor .~ pure (mkColor color)
  205 
  206 withStrokeColorPixel :: PixelRGBA8 -> Tree -> Tree
  207 withStrokeColorPixel color = strokeColor .~ pure (ColorRef color)
  208 
  209 withStrokeDashArray :: [Double] -> Tree -> Tree
  210 withStrokeDashArray arr = strokeDashArray .~ pure (map Num arr)
  211 
  212 -- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin>
  213 withStrokeLineJoin :: LineJoin -> Tree -> Tree
  214 withStrokeLineJoin ljoin = strokeLineJoin .~ pure ljoin
  215 
  216 -- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill>
  217 withFillColor :: String -> Tree -> Tree
  218 withFillColor color = fillColor .~ pure (mkColor color)
  219 
  220 withFillColorPixel :: PixelRGBA8 -> Tree -> Tree
  221 withFillColorPixel color = fillColor .~ pure (ColorRef color)
  222 
  223 -- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-opacity>
  224 withFillOpacity :: Double -> Tree -> Tree
  225 withFillOpacity opacity = fillOpacity ?~ realToFrac opacity
  226 
  227 withGroupOpacity :: Double -> Tree -> Tree
  228 withGroupOpacity opacity = groupOpacity ?~ realToFrac opacity
  229 
  230 -- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width>
  231 withStrokeWidth :: Double -> Tree -> Tree
  232 withStrokeWidth width = strokeWidth .~ pure (Num width)
  233 
  234 -- | See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-path>
  235 withClipPathRef :: ElementRef -- ^ Reference to clip path defined previously (e.g. by 'mkClipPath')
  236                 -> Tree -- ^ Image that will be clipped by the referenced clip path
  237                 -> Tree
  238 withClipPathRef ref sub = mkGroup [sub] & clipPathRef .~ pure ref
  239 
  240 -- | Assigns ID attribute to given image.
  241 withId :: String -> Tree -> Tree
  242 withId idTag = attrId ?~ idTag
  243 
  244 -- | @mkRect width height@ creates a rectangle with given @with@ and @height@, centered at @(0, 0)@.
  245 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect>
  246 mkRect :: Double -> Double -> Tree
  247 mkRect width height = translate (-width/2) (-height/2) $ RectangleTree $ defaultSvg
  248   & rectUpperLeftCorner .~ (Num 0, Num 0)
  249   & rectWidth ?~ Num width
  250   & rectHeight ?~ Num height
  251 
  252 -- | Create a circle with given radius, centered at @(0, 0)@.
  253 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle>
  254 mkCircle :: Double -> Tree
  255 mkCircle radius = CircleTree $ defaultSvg
  256   & circleCenter .~ (Num 0, Num 0)
  257   & circleRadius .~ Num radius
  258 
  259 -- | Create an ellipse given X-axis radius, and Y-axis radius, with center at @(0, 0)@.
  260 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse>
  261 mkEllipse :: Double -> Double -> Tree
  262 mkEllipse rx ry = EllipseTree $ defaultSvg
  263   & ellipseCenter .~ (Num 0, Num 0)
  264   & ellipseXRadius .~ Num rx
  265   & ellipseYRadius .~ Num ry
  266 
  267 -- | Create a line segment between two points given by their @(x, y)@ coordinates.
  268 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line>
  269 mkLine :: (Double,Double) -> (Double, Double) -> Tree
  270 mkLine (x1,y1) (x2,y2) = LineTree $ defaultSvg
  271   & linePoint1 .~ (Num x1, Num y1)
  272   & linePoint2 .~ (Num x2, Num y2)
  273 
  274 -- | Merges multiple images into one.
  275 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g>
  276 mkGroup :: [Tree] -> Tree
  277 mkGroup forest = GroupTree $ defaultSvg
  278   & groupChildren .~ forest
  279 
  280 -- | Create definition of graphical objects that can be used at later time.
  281 --   See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs>
  282 mkDefinitions :: [Tree] -> Tree
  283 mkDefinitions forest = DefinitionTree $ defaultSvg
  284   & groupChildren .~ forest
  285 
  286 -- | Create an element by referring to existing element defined previously.
  287 -- For example you can create a graphical element, assign ID to it using 'withId', wrap it in
  288 -- 'mkDefinitions' and then use it via @use "myId"@.
  289 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use>
  290 mkUse :: String -> Tree
  291 mkUse name = UseTree (defaultSvg & useName .~ name) Nothing
  292 
  293 -- | A clip path restricts the region to which paint can be applied.
  294 -- See <https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath>
  295 mkClipPath :: String  -- ^ ID of the clip path, which can then be referred to by other elements
  296                       --   using 'withClipPathRef'.
  297            -> [Tree] -- ^ List of shapes that will determine the final shape of the clipping region
  298            -> Tree
  299 mkClipPath idTag forest = withId idTag $ ClipPathTree $ defaultSvg
  300   & clipPathContent .~ forest
  301 
  302 -- | Create a path from the list of path commands.
  303 --   See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Path_commands>
  304 mkPath :: [PathCommand] -> Tree
  305 mkPath cmds = PathTree $ defaultSvg & pathDefinition .~ cmds
  306 
  307 -- | Similar to 'mkPathText', but taking SVG path command as a String.
  308 mkPathString :: String -> Tree
  309 mkPathString = mkPathText . T.pack
  310 
  311 -- | Create path from textual representation of SVG path command.
  312 --   If the text doesn't represent valid path command, this function fails with 'Prelude.error'.
  313 --   Use 'mkPath' for type safe way of creating paths.
  314 mkPathText :: T.Text -> Tree
  315 mkPathText str =
  316   case parseOnly pathParser str of
  317     Left err   -> error err
  318     Right cmds -> mkPath cmds
  319 
  320 -- | Create a path from a list of @(x, y)@ coordinates of points along the path.
  321 mkLinePath :: [(Double, Double)] -> Tree
  322 mkLinePath [] = mkGroup []
  323 mkLinePath ((startX, startY):rest) =
  324     PathTree $ defaultSvg & pathDefinition .~ cmds
  325   where
  326     cmds = [ MoveTo OriginAbsolute [V2 startX startY]
  327            , LineTo OriginAbsolute [ V2 x y | (x, y) <- rest ] ]
  328 
  329 -- | Create a path from a list of @(x, y)@ coordinates of points along the path.
  330 mkLinePathClosed :: [(Double, Double)] -> Tree
  331 mkLinePathClosed [] = mkGroup []
  332 mkLinePathClosed ((startX, startY):rest) =
  333     PathTree $ defaultSvg & pathDefinition .~ cmds
  334   where
  335     cmds = [ MoveTo OriginAbsolute [V2 startX startY]
  336            , LineTo OriginAbsolute [ V2 x y | (x, y) <- rest ]
  337            , EndPath ]
  338 
  339 -- | Rectangle with a uniform color and the same size as the screen.
  340 --
  341 --   Example:
  342 --
  343 --   > animate $ const $ mkBackground "yellow"
  344 --
  345 --   <<docs/gifs/doc_mkBackground.gif>>
  346 mkBackground :: String -> Tree
  347 mkBackground color = withFillOpacity 1 $  withStrokeWidth 0 $
  348   withFillColor color $ mkRect screenWidth screenHeight
  349 
  350 mkBackgroundPixel :: PixelRGBA8 -> Tree
  351 mkBackgroundPixel pixel =
  352     withFillOpacity 1 $ withStrokeWidth 0 $
  353     withFillColorPixel pixel $ mkRect screenWidth screenHeight
  354 
  355 -- | Take list of rows, where each row consists of number of images and display them in regular
  356 --   grid structure.
  357 --   All rows will get equal amount of vertical space.
  358 --   The images within each row will get equal amount of horizontal space, independent of the other
  359 --   rows. Each row can contain different number of cells.
  360 gridLayout :: [[Tree]] -> Tree
  361 gridLayout rows = mkGroup
  362     [ translate (-screenWidth/2+colSep*nCol + colSep*0.5)
  363                 (screenHeight/2-rowSep*nRow - rowSep*0.5)
  364       elt
  365     | (nRow, row) <- zip [0..] rows
  366     , let nCols = length row
  367           colSep = screenWidth / fromIntegral nCols
  368     , (nCol, elt) <- zip [0..] row ]
  369   where
  370     rowSep = screenHeight / fromIntegral nRows
  371     nRows = length rows
  372 
  373 -- | Insert a native text object anchored at the middle.
  374 --
  375 --   Example:
  376 --
  377 --   > mkAnimation 2 $ \t -> scale 2 $ withStrokeWidth 0.05 $ mkText (T.take (round $ t*15) "text")
  378 --
  379 --   <<docs/gifs/doc_mkText.gif>>
  380 mkText :: T.Text -> Tree
  381 mkText str =
  382   flipYAxis
  383   (TextTree Nothing $ defaultSvg
  384     & textRoot .~ span_
  385     & fontSize .~ pure (Num 2))
  386     & textAnchor .~ pure TextAnchorMiddle
  387     -- Note: TextAnchorMiddle is placed on the 'flipYAxis' group such that it can easily
  388     -- be overwritten by the user.
  389   where
  390     span_ = defaultSvg & spanContent .~ [SpanText str]
  391 
  392 -- | Switch from the default viewbox to a custom viewbox. Nesting custom viewboxes is
  393 --   unlikely to give good results. If you need nested custom viewboxes, you will have
  394 --   to configure them by hand.
  395 --
  396 --   The viewbox argument is (min-x, min-y, width, height).
  397 --
  398 --   Example:
  399 --
  400 --   > withViewBox (0,0,1,1) $ mkBackground "yellow"
  401 --
  402 --   <<docs/gifs/doc_withViewBox.gif>>
  403 withViewBox :: (Double, Double, Double, Double) -> Tree -> Tree
  404 withViewBox vbox child = translate (-screenWidth/2) (-screenHeight/2) $
  405   SvgTree $ Document
  406   { _viewBox = Just vbox
  407   , _width = Just (Num screenWidth)
  408   , _height = Just (Num screenHeight)
  409   , _elements = [child]
  410   , _description = ""
  411   , _documentLocation = ""
  412   , _documentAspectRatio = PreserveAspectRatio False AlignNone Nothing
  413   }