{-# LANGUAGE GADTs #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Rendering.Chart.Backend.Impl
-- Copyright   :  (c) Tim Docker 2014
-- License     :  BSD-style (see chart/COPYRIGHT)
--
-- This module provides the implementation details common to all 'ChartBackend's.

module Graphics.Rendering.Chart.Backend.Impl where

import Control.Monad.Reader
import Control.Monad.Operational

import Graphics.Rendering.Chart.Geometry
import Graphics.Rendering.Chart.Backend.Types

-- -----------------------------------------------------------------------
-- Rendering Backend Class
-- -----------------------------------------------------------------------

-- | The abstract drawing operation generated when using the
--   the chart drawing API.
--   
--   See the documentation of the different function for the correct semantics
--   of each instruction:
--   
--   * 'strokePath', 'fillPath'
--   
--   * 'drawText', 'textSize'
--   
--   * 'getPointAlignFn', 'getCoordAlignFn', 'AlignmentFns'
--   
--   * 'withTransform', 'withClipRegion'
--   
--   * 'withLineStyle', 'withFillStyle', 'withFontStyle'
--   
data ChartBackendInstr a where
  StrokePath :: Path -> ChartBackendInstr ()
  FillPath   :: Path -> ChartBackendInstr ()
  GetTextSize :: String -> ChartBackendInstr TextSize
  DrawText    :: Point -> String -> ChartBackendInstr ()
  GetAlignments :: ChartBackendInstr AlignmentFns
  WithTransform  :: Matrix ->  Program ChartBackendInstr a -> ChartBackendInstr a
  WithFontStyle  :: FontStyle -> Program ChartBackendInstr a -> ChartBackendInstr a
  WithFillStyle  :: FillStyle -> Program ChartBackendInstr a -> ChartBackendInstr a
  WithLineStyle  :: LineStyle -> Program ChartBackendInstr a -> ChartBackendInstr a
  WithClipRegion :: Rect -> Program ChartBackendInstr a -> ChartBackendInstr a

-- | A 'BackendProgram' provides the capability to render a chart somewhere.
--   
--   The coordinate system of the backend has its initial origin (0,0)
--   in the top left corner of the drawing plane. The x-axis points 
--   towards the top right corner and the y-axis points towards 
--   the bottom left corner. The unit used by coordinates, the font size,
--   and lengths is the always the same, but depends on the backend.
--   All angles are measured in radians.
--   
--   The line, fill and font style are set to their default values 
--   initially.
--   
--   Information about the semantics of the instructions can be 
--   found in the documentation of 'ChartBackendInstr'.
type BackendProgram a = Program ChartBackendInstr a

-- | Stroke the outline of the given path using the 
--   current 'LineStyle'. This function does /not/ perform
--   alignment operations on the path. See 'Path' for the exact semantic
--   of paths.
strokePath :: Path -> BackendProgram ()
strokePath :: Path -> BackendProgram ()
strokePath Path
p = ChartBackendInstr () -> BackendProgram ()
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (Path -> ChartBackendInstr ()
StrokePath Path
p)

-- | Fill the given path using the current 'FillStyle'.
--   The given path will be closed prior to filling.
--   This function does /not/ perform
--   alignment operations on the path.
--   See 'Path' for the exact semantic of paths.
fillPath :: Path -> BackendProgram ()
fillPath :: Path -> BackendProgram ()
fillPath Path
p = ChartBackendInstr () -> BackendProgram ()
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (Path -> ChartBackendInstr ()
FillPath Path
p)

-- | Calculate a 'TextSize' object with rendering information
--   about the given string without actually rendering it.
textSize :: String -> BackendProgram TextSize
textSize :: String -> BackendProgram TextSize
textSize String
text = ChartBackendInstr TextSize -> BackendProgram TextSize
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (String -> ChartBackendInstr TextSize
GetTextSize String
text)

-- | Draw a single-line textual label anchored by the baseline (vertical) 
--   left (horizontal) point. Uses the current 'FontStyle' for drawing.
drawText :: Point -> String -> BackendProgram ()
drawText :: Point -> String -> BackendProgram ()
drawText Point
p String
text = ChartBackendInstr () -> BackendProgram ()
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (Point -> String -> ChartBackendInstr ()
DrawText Point
p String
text)

-- | Apply the given transformation in this local
--   environment when drawing. The given transformation 
--   is applied after the current transformation. This
--   means both are combined.
withTransform :: Matrix -> BackendProgram a -> BackendProgram a
withTransform :: Matrix -> BackendProgram a -> BackendProgram a
withTransform Matrix
t BackendProgram a
p = ChartBackendInstr a -> BackendProgram a
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (Matrix -> BackendProgram a -> ChartBackendInstr a
forall a.
Matrix -> Program ChartBackendInstr a -> ChartBackendInstr a
WithTransform Matrix
t BackendProgram a
p)

-- | Use the given font style in this local
--   environment when drawing text.
--   
--   An implementing backend is expected to guarentee
--   to support the following font families: @serif@, @sans-serif@ and @monospace@;
--   
--   If the backend is not able to find or load a given font 
--   it is required to fall back to a custom fail-safe font
--   and use it instead.
withFontStyle :: FontStyle -> BackendProgram a -> BackendProgram a
withFontStyle :: FontStyle -> BackendProgram a -> BackendProgram a
withFontStyle FontStyle
fs BackendProgram a
p = ChartBackendInstr a -> BackendProgram a
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (FontStyle -> BackendProgram a -> ChartBackendInstr a
forall a.
FontStyle -> Program ChartBackendInstr a -> ChartBackendInstr a
WithFontStyle FontStyle
fs BackendProgram a
p)

-- | Use the given fill style in this local
--   environment when filling paths.
withFillStyle :: FillStyle -> BackendProgram a -> BackendProgram a
withFillStyle :: FillStyle -> BackendProgram a -> BackendProgram a
withFillStyle FillStyle
fs BackendProgram a
p = ChartBackendInstr a -> BackendProgram a
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (FillStyle -> BackendProgram a -> ChartBackendInstr a
forall a.
FillStyle -> Program ChartBackendInstr a -> ChartBackendInstr a
WithFillStyle FillStyle
fs BackendProgram a
p)

-- | Use the given line style in this local
--   environment when stroking paths.
withLineStyle :: LineStyle -> BackendProgram a -> BackendProgram a
withLineStyle :: LineStyle -> BackendProgram a -> BackendProgram a
withLineStyle LineStyle
ls BackendProgram a
p = ChartBackendInstr a -> BackendProgram a
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (LineStyle -> BackendProgram a -> ChartBackendInstr a
forall a.
LineStyle -> Program ChartBackendInstr a -> ChartBackendInstr a
WithLineStyle LineStyle
ls BackendProgram a
p)

-- | Use the given clipping rectangle when drawing
--   in this local environment. The new clipping region
--   is intersected with the given clip region. You cannot 
--   escape the clip!
withClipRegion :: Rect -> BackendProgram a -> BackendProgram a
withClipRegion :: Rect -> BackendProgram a -> BackendProgram a
withClipRegion Rect
c BackendProgram a
p = ChartBackendInstr a -> BackendProgram a
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton (Rect -> BackendProgram a -> ChartBackendInstr a
forall a.
Rect -> Program ChartBackendInstr a -> ChartBackendInstr a
WithClipRegion Rect
c BackendProgram a
p)

-- -----------------------------------------------------------------------
-- Rendering Utility Functions
-- -----------------------------------------------------------------------

-- | Get the point alignment function
getPointAlignFn :: BackendProgram (Point->Point)
getPointAlignFn :: BackendProgram (Point -> Point)
getPointAlignFn = (AlignmentFns -> Point -> Point)
-> ProgramT ChartBackendInstr Identity AlignmentFns
-> BackendProgram (Point -> Point)
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM AlignmentFns -> Point -> Point
afPointAlignFn (ChartBackendInstr AlignmentFns
-> ProgramT ChartBackendInstr Identity AlignmentFns
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton ChartBackendInstr AlignmentFns
GetAlignments)

-- | Get the coordinate alignment function
getCoordAlignFn :: BackendProgram (Point->Point)
getCoordAlignFn :: BackendProgram (Point -> Point)
getCoordAlignFn = (AlignmentFns -> Point -> Point)
-> ProgramT ChartBackendInstr Identity AlignmentFns
-> BackendProgram (Point -> Point)
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM AlignmentFns -> Point -> Point
afCoordAlignFn (ChartBackendInstr AlignmentFns
-> ProgramT ChartBackendInstr Identity AlignmentFns
forall (instr :: * -> *) a (m :: * -> *).
instr a -> ProgramT instr m a
singleton ChartBackendInstr AlignmentFns
GetAlignments)