{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveFunctor #-}
---------------------------------------------------------
-- |
-- Copyright   : (c) 2006-2016, alpheccar.org
-- License     : BSD-style
--
-- Maintainer  : misc@NOSPAMalpheccar.org
-- Stability   : experimental
-- Portability : portable
--
-- PDF Text
---------------------------------------------------------
{-# LANGUAGE FlexibleContexts #-}
module Graphics.PDF.Text(
   -- * Text
   -- ** Types
     PDFFont(..)
   , FontName(..)
   , TextMode(..)
   , PDFText
   , UnscaledUnit
   -- ** Functions
   , drawText
   , text
   , startNewLine
   , displayGlyphs
   , displayText
   , textStart
   , setFont
   , leading
   , charSpace
   , wordSpace
   , textScale
   , renderMode
   , rise
   , setTextMatrix
   , textWidth
   , pdfGlyph
   , glyph
 ) where

import Graphics.PDF.LowLevel.Types
import Graphics.PDF.Draw
import Control.Monad.State
import Graphics.PDF.Resources
import Control.Monad.Writer
import qualified Data.Set as Set
import Data.List(foldl')
import Data.Binary.Builder(Builder)
import Graphics.PDF.LowLevel.Serializer
import qualified Data.ByteString as S
import qualified Data.Text as T
import Graphics.PDF.Fonts.Font
import Graphics.PDF.Fonts.StandardFont


glyphStreamWidth :: PDFFont
                 -> PDFGlyph
                 -> PDFFloat
glyphStreamWidth (PDFFont f s) (PDFGlyph t) =
 let w = foldl' (\a b -> a + glyphWidth f s (fromIntegral b)) 0 . S.unpack $ t
 in
  w + (foldl' (\a (x,y) -> a + getKern f s x y) 0 $ [(GlyphCode ca,GlyphCode cb) | (ca,cb) <- S.zip t (S.tail t)])

textWidth :: PDFFont -> T.Text -> PDFFloat
textWidth f t = glyphStreamWidth f . pdfGlyph f $ t

pdfGlyph :: PDFFont
         -> T.Text
         -> PDFGlyph
pdfGlyph (PDFFont f _) t = PDFGlyph . S.pack . map (fromIntegral . charGlyph f) . T.unpack $ t


type FontState = (Set.Set AnyFont)

data TextParameter = TextParameter { tc :: !PDFFloat
                                   , tw :: !PDFFloat
                                   , tz :: !PDFFloat
                                   , tl :: !PDFFloat
                                   , ts :: !PDFFloat
                                   , fontState :: FontState
                                   , currentFont :: Maybe PDFFont
                                   }
defaultParameters ::  TextParameter
defaultParameters = TextParameter 0 0 100 0 0 (Set.empty) Nothing


-- | The text monad 
newtype PDFText a = PDFText {unText :: WriterT Builder (State TextParameter) a}
#ifndef __HADDOCK__                    
  deriving(Monad,Applicative,Functor,MonadWriter Builder,MonadState TextParameter)
#else
instance Monad PDFText
instance Functor PDFText
instance MonadWriter Builder PDFText
instance MonadState TextParameter PDFText
#endif

instance MonadPath PDFText

-- | Unscaled unit (not scaled by the font size)
type UnscaledUnit = PDFFloat

-- | Rendering mode for text display
data TextMode = FillText
              | StrokeText
              | FillAndStrokeText
              | InvisibleText
              | FillTextAndAddToClip
              | StrokeTextAndAddToClip
              | FillAndStrokeTextAndAddToClip
              | AddToClip
              deriving(Eq,Ord,Enum)

-- | Select a font to use
setFont :: PDFFont -> PDFText ()
setFont f@(PDFFont n size) = PDFText $ do
    lift (modifyStrict $ \s -> s {fontState = Set.insert n (fontState s), currentFont = Just f})
    tell . mconcat$ [ serialize "\n/"
                    , serialize (name n)
                    , serialize ' '
                    , toPDF size
                    , serialize " Tf"
                    ]


-- | Draw a text in the draw monad
drawText :: PDFText a
         -> Draw a
drawText t = do
    let ((a,w),s) = (runState . runWriterT . unText $ t) defaultParameters
    mapM_ addFontRsrc (Set.elems (fontState s))
    tell . serialize $ "\nBT"
    tell w
    tell . serialize $ "\nET"
    return a
 where
   addFontRsrc font = modifyStrict $ \s ->
       s { rsrc = addResource (PDFName "Font") (PDFName (name font)) (toRsrc font) (rsrc s)}

-- | Set position for the text beginning
textStart :: PDFFloat
          -> PDFFloat
          -> PDFText ()
textStart x y = tell . mconcat  $ [ serialize '\n'
                                  , toPDF x
                                  , serialize ' '
                                  , toPDF y
                                  , serialize " Td"
                                  ]
 --writeCmd $ "\n" ++ (show x) ++ " " ++ (show y) ++ " Td"         


glyph :: GlyphCode -> PDFGlyph
glyph c = PDFGlyph . S.singleton $ (fromIntegral c)

-- | Display glyphs
displayGlyphs :: PDFGlyph
              -> PDFText ()
displayGlyphs t = do
    tell $ serialize ' '
    tell . toPDF $ t
    tell . serialize $ " Tj"

-- | Display text
displayText :: T.Text
            -> PDFText ()
displayText t = do
    f <- gets currentFont
    case f of
      Nothing -> return ()
      Just aFont -> do
         let g = pdfGlyph aFont t
         displayGlyphs g


--    f <- gets currentFont
--    let rt = ripText f t
--    tell . serialize $ '\n'
--    tell lbracket
--    mapM_ displayGlyphs rt
--    tell rbracket
--    tell $ serialize " TJ"
-- where
-- 	displayGlyphs (w,c) = do
-- 		tell $ toPDF (toPDFString $ c:[])
-- 		tell bspace
-- 		tell . toPDF $ w
-- 		tell bspace


-- | Start a new line (leading value must have been set)
startNewLine :: PDFText ()
startNewLine = tell . serialize $ "\nT*"

-- | Set leading value
leading :: UnscaledUnit -> PDFText ()
leading v = PDFText $ do
    lift (modifyStrict $ \s -> s {tl = v})
    tell . mconcat $  [ serialize '\n'
                      , toPDF v
                      , serialize " TL"
                      ]

-- | Set the additional char space
charSpace :: UnscaledUnit -> PDFText ()
charSpace v = PDFText $ do
    lift (modifyStrict $ \s -> s {tc = v})
    tell . mconcat  $ [ serialize '\n'
                      , toPDF v
                      , serialize " Tc"
                      ]

-- | Set the additional word space
wordSpace :: UnscaledUnit -> PDFText ()
wordSpace v = PDFText $ do
    lift (modifyStrict $ \s -> s {tw = v})
    tell . mconcat $ [ serialize '\n'
                      , toPDF v
                      , serialize " Tw"
                      ]

-- | Set scaling factor for text
textScale :: PDFFloat -> PDFText ()
textScale v = PDFText $ do
    lift (modifyStrict $ \s -> s {tz = v})
    tell . mconcat  $ [ serialize '\n'
                      , toPDF v
                      , serialize " Tz"
                      ]

-- | Choose the text rendering mode
renderMode :: TextMode -> PDFText ()
renderMode v =
    tell . mconcat $  [ serialize '\n'
                      , toPDF (fromEnum v)
                      , serialize " Tr"
                      ]

-- | Set the rise value
rise :: UnscaledUnit -> PDFText ()
rise v = PDFText $ do
    lift (modifyStrict $ \s -> s {ts = v})
    tell . mconcat $  [ serialize '\n'
                      , toPDF v
                      , serialize " Ts"
                      ]

-- | Set the text transformation matrix
setTextMatrix :: Matrix -> PDFText()
setTextMatrix (Matrix a b c d e f) =
    tell . mconcat $[ serialize '\n'
                    , toPDF a
                    , serialize ' '
                    , toPDF b
                    , serialize ' '
                    , toPDF c
                    , serialize ' '
                    , toPDF d
                    , serialize ' '
                    , toPDF e
                    , serialize ' '
                    , toPDF f
                    , serialize " Tm"
                    ]

-- | Utility function to quickly display one line of text
text :: PDFFont
     -> PDFFloat
     -> PDFFloat
     -> T.Text
     -> PDFText ()
text f x y t = do
    setFont f
    let g = pdfGlyph f t
    textStart x y
    displayGlyphs g