{-# LANGUAGE Rank2Types #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Diagrams.TwoD.Adjust
-- Copyright   :  (c) 2011 diagrams-lib team (see LICENSE)
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  diagrams-discuss@googlegroups.com
--
-- A default diagram-adjustment implementation for two-dimensional
-- diagrams, useful for backend implementors.
--
-----------------------------------------------------------------------------

module Diagrams.TwoD.Adjust
    (
      setDefault2DAttributes
    , adjustDiaSize2D
    , adjustDia2D
    ) where

import           Diagrams.Attributes      (lineCap, lineJoin,
                                           lineMiterLimitA)
import           Diagrams.Core
import           Diagrams.TwoD.Attributes (lineWidthA, lineTextureA)
import           Diagrams.TwoD.Size       (SizeSpec2D (..), center2D,
                                           requiredScale, size2D)
import           Diagrams.TwoD.Text       (fontSizeA)
import           Diagrams.TwoD.Types      (R2, T2, p2)
import           Diagrams.Util            (( # ))

import           Control.Lens             (Lens', (&), (.~), (^.))
import           Data.AffineSpace         ((.-.))
import           Data.Default.Class
import           Data.Semigroup

-- | Set default attributes of a 2D diagram (in case they have not
--   been set):
--
--       * Line width 0.01
--
--       * Line color black
--
--       * Font size 1
--
--       * Line cap LineCapButt
--
--       * line join miter
--
--       * Miter limit 10
setDefault2DAttributes :: Semigroup m => QDiagram b R2 m -> QDiagram b R2 m
setDefault2DAttributes d = d # lineWidthA def # lineTextureA def # fontSizeA def
                             # lineCap def # lineJoin def # lineMiterLimitA def


-- | Adjust the size and position of a 2D diagram to fit within the
--   requested size. The first argument is a lens into the output
--   size contained in the rendering options.  Returns an updated
--   options record, any transformation applied to the diagram (the
--   inverse of which can be used, say, to translate output/device
--   coordinates back into local diagram coordinates), and the
--   modified diagram itself.
adjustDiaSize2D :: Monoid' m
                => Lens' (Options b R2) SizeSpec2D
                -> b -> Options b R2 -> QDiagram b R2 m
                -> (Options b R2, T2, QDiagram b R2 m)
adjustDiaSize2D szL _ opts d =
  ( case spec of
     Dims _ _ -> opts
     _        -> opts & szL .~ (uncurry Dims . scale s $ size)
  , adjustT
  , d # transform adjustT
  )
  where spec = opts ^. szL
        size = size2D d
        s    = requiredScale spec size
        finalSz = case spec of
                    Dims w h -> (w,h)
                    _        -> scale s size
        tr = (0.5 *. p2 finalSz) .-. (s *. center2D d)
        adjustT = translation tr <> scaling s

-- | @adjustDia2D@ provides a useful default implementation of
--   the 'adjustDia' method from the 'Backend' type class.
--
--   As its first argument it requires a lens into the output size
--   contained in the rendering options.
--
--   It then performs the following adjustments:
--
--   * Set default attributes (see 'setDefault2DAttributes')
--
--   * Scale and translate the diagram to fit within the requested
--     size (see 'adjustDiaSize2D')
--
--   It returns an updated options record, any transformation applied
--   to the diagram (the inverse of which can be used, say, to
--   translate output/device coordinates back into local diagram
--   coordinates), and the modified diagram itself.
adjustDia2D :: Monoid' m
            => Lens' (Options b R2) SizeSpec2D
            -> b -> Options b R2 -> QDiagram b R2 m
            -> (Options b R2, T2, QDiagram b R2 m)
adjustDia2D szL b opts d
  = adjustDiaSize2D szL b opts (d # setDefault2DAttributes)