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