-----------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Rendering.Diagrams.Attributes
-- Copyright   :  (c) Brent Yorgey 2008
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  byorgey@gmail.com
-- Stability   :  experimental
-- Portability :  portable
--
-- Attributes which can be added as annotations to a 'Diagram',
-- implemented via instances of 'AttrClass'.
--
-----------------------------------------------------------------------------
module Graphics.Rendering.Diagrams.Attributes
  ( defaultAttributes
  , fillColor, fc
  , lineColor, lc
  , lineWidth, lw
  , lineCap, C.LineCap(..)
  , lineJoin, C.LineJoin(..)
  , dashing

  , typeface, tf

  , stretch, scale, scaleX, scaleY
  , translate, translateX, translateY
  , rotate, rotateR

  ) where

import Graphics.Rendering.Diagrams.Types

import qualified Graphics.Rendering.Cairo as C


-- Fill color ----------------------

-- | Specify the default fill color for a 'Diagram'.
newtype Fill = Fill SomeColor
instance AttrClass Fill where
  renderAttr (Fill (SomeColor f)) = return (setEnvFillColor f)

-- | Draw a diagram using the given fill color.  Note that the new
--   color only applies to parts of the diagram which are not
--   otherwise colored; subdiagrams which already have an explicit
--   fill color will not be affected.  The default fill color is
--   completely transparent.
fillColor :: Color c => c -> Diagram -> Diagram
fillColor = Ann . Attr . Fill . SomeColor

-- | 'fc' is provided as a convenient short synonym for 'fillColor'.
fc :: Color c => c -> Diagram -> Diagram
fc = fillColor

-- Stroke color ----------------------

-- | Specify the default stroke color for a 'Diagram'.
newtype Stroke = Stroke SomeColor
instance AttrClass Stroke where
  renderAttr (Stroke (SomeColor sc)) = return (setEnvStrokeColor sc)

-- | Draw a diagram using the given color for lines.   Note that the new
--   color only applies to parts of the diagram which are not
--   otherwise colored; subdiagrams which already have an explicit
--   line color will not be affected.  The default line color is black.
lineColor :: Color c => c -> Diagram -> Diagram
lineColor = Ann . Attr . Stroke . SomeColor

-- | 'lc' is provided as a convenient short synonym for 'lineColor'.
lc :: Color c => c -> Diagram -> Diagram
lc = lineColor


-- Stroke width ------------------------------

-- | The stroke width to be used in drawing lines or shape outlines.
--   Note that the stroke width is measured in /device coordinates/,
--   so a stroke width of a certain size will look the same under any
--   uniform scaling.  Under non-uniform (i.e. different in the x and
--   y axes) scaling, however, strokes may look distorted.
newtype StrokeWidth = StrokeWidth Double  deriving (Eq, Show, Read)
instance AttrClass StrokeWidth where
  renderAttr (StrokeWidth w) = return (setEnvStrokeWidth w)

-- | Draw shape outlines and lines with the given width.  Note that
--   the line width is invariant under uniform scaling, although under
--   non-uniform scaling (scaling by different amounts in the x and y
--   axes) lines can become distorted.  The default line width is 1.
lineWidth :: Double -> Diagram -> Diagram
lineWidth = Ann . Attr . StrokeWidth

-- | 'lw' is provided as a convenient short synonym for 'lineWidth'.
lw :: Double -> Diagram -> Diagram
lw = lineWidth

-- Fonts -----------------------------

-- | Set the typeface for a text diagram
newtype Typeface = Typeface String deriving (Eq, Show, Read)
instance AttrClass Typeface where
  renderAttr (Typeface fontname) = c $ C.selectFontFace fontname C.FontSlantNormal C.FontWeightNormal >> return id

-- | Change the default typeface to one named.
typeface :: String -> Diagram -> Diagram
typeface fontname = Ann (Attr (Typeface fontname))

-- | Convenience function to change the typeface.
tf :: String -> Diagram -> Diagram
tf = typeface

-- Rotate ----------------------------

-- | Rotate a diagram clockwise through a certain number of radians.
newtype Rotate = Rotate Double  deriving (Eq, Show, Read)
instance AttrClass Rotate where
  renderAttr (Rotate d) = c $ C.rotate d >> return id

-- | @rotateR r@ rotates a diagram clockwise by @r@ radians.
rotateR :: Double -> Diagram -> Diagram
rotateR r = Ann (Attr (Rotate r))

-- | @rotate f@ rotates a diagram clockwise by fraction @f@ of a
--   complete revolution.  @rotate f@ is equivalent to @rotateR
--   (2*pi*f)@.
rotate :: Double -> Diagram -> Diagram
rotate f = rotateR (2*pi*f)


-- Translate ---------------------------

-- | Translate a diagram by the given offset.
newtype Translate = Translate Point  deriving (Eq, Show, Read)
instance AttrClass Translate where
  renderAttr (Translate (x,y)) = c $ C.translate x y >> return id

-- | Translate a diagram by the given relative offsets in the x and y
--   directions.  Note that the positive x-axis is to the right, while
--   the positive y-axis points downwards.
translate :: Double -> Double -> Diagram -> Diagram
translate 0  0  = id
translate dx dy = Ann (Attr (Translate (dx,dy)))

-- | Translate a diagram along the x-axis only.  @translateX x@ is
--   equivalent to @translate x 0@.
translateX :: Double -> Diagram -> Diagram
translateX dx = translate dx 0

-- | Translate a diagram along the y-axis only.  @translateY y@ is
--   equivalent to @translate 0 y@.
translateY :: Double -> Diagram -> Diagram
translateY dy = translate 0 dy



-- Scale --------------------------------

-- | Scale a diagram by the given scaling factors in the x and y axes,
--   respectively.
newtype Scale = Scale Point  deriving (Eq, Show, Read)
instance AttrClass Scale where
  attrSize (Scale k) s = k .*. s
  renderAttr (Scale (x,y)) = c $ C.scale x y >> return id

-- | Stretch a diagram by a separate scaling factor for each axis.
--   @stretch w h@ scales by a factor of @w@ in the x direction and
--   a factor of @h@ in the y direction.
stretch :: Double -> Double -> Diagram -> Diagram
stretch sx sy = Ann (Attr (Scale (sx,sy)))

-- | Scale by the same scaling factor in both dimensions, so the diagram
--   retains its aspect ratio.
scale :: Double -> Diagram -> Diagram
scale 1 = id
scale s = stretch s s

-- | Scale a diagram along the x-axis only.  @scaleX s@ is equivalent
--   to @stretch s 1@.
scaleX :: Double -> Diagram -> Diagram
scaleX sx = stretch sx 1

-- | Scale a diagram along the y-axis only.  @scaleY s@ is equivalent
--   to @stretch 1 s@.
scaleY :: Double -> Diagram -> Diagram
scaleY sy = stretch 1 sy


-- Stroke styles ----------------------------------

newtype LCap = LCap C.LineCap
instance AttrClass LCap where
  renderAttr (LCap lcap) = c $ C.setLineCap lcap >> return id

-- | Set the line cap style.  Valid values for 'LineCap' are
--   @LineCapButt@, @LineCapRound@, and @LineCapSquare@.
lineCap :: C.LineCap -> Diagram -> Diagram
lineCap = Ann . Attr . LCap

newtype LJoin = LJoin C.LineJoin
instance AttrClass LJoin where
  renderAttr (LJoin lj) = c $ C.setLineJoin lj >> return id

-- | Set the line join style.  Valid values for 'LineJoin' are
--   @LineJoinMiter@, @LineJoinRound@, and @LineJoinBevel@.
lineJoin :: C.LineJoin -> Diagram -> Diagram
lineJoin = Ann . Attr . LJoin

data Dashing = Dashing [Double] Double
instance AttrClass Dashing where
  renderAttr (Dashing ds offs) = c $ C.setDash ds offs >> return id

-- | Set the line dashing pattern.
dashing :: [Double]  -- ^ a list specifying alternate lengths of on
                     --   and off portions of the stroke.  The empty
                     --   list indicates no dashing.
        -> Double    -- ^ an offset into the dash pattern at which the
                     --   stroke should start
        -> Diagram -> Diagram
dashing ds offs = Ann (Attr (Dashing ds offs))

-- | Apply all the default attributes to a 'Diagram'.
defaultAttributes :: Diagram -> Diagram
defaultAttributes =
  case defaultDiaRenderEnv of
    (DREnv (SomeColor fillC) (SomeColor strokeC) strokeW) ->
      lc strokeC .
      lw strokeW .
      lineCap C.LineCapButt .
      lineJoin C.LineJoinMiter .
      dashing [] 0 .
      fc fillC