    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Graphics.HGL.Draw.Text
    -- Copyright   :  (c) Alastair Reid, 1999-2003
    -- License     :  BSD-style (see the file libraries/base/LICENSE)
    --
    -- Maintainer  :  libraries@haskell.org
    -- Stability   :  provisional
    -- Portability :  non-portable (requires concurrency)
    --
    -- Drawing text.
    --
    -----------------------------------------------------------------------------

    module Graphics.HGL.Draw.Text
            (
    	-- * Drawing text
              text
    	-- ToDo: add textInfo to Win32
    #if !X_DISPLAY_MISSING
            , textInfo
    #endif
    	-- * Color
    	, RGB(RGB)
    	, setTextColor		-- :: RGB -> Draw RGB
    	, setBkColor		-- :: RGB -> Draw RGB
    	, BkMode(Opaque, Transparent)
    	, setBkMode		-- :: BkMode -> Draw BkMode
    	-- * Alignment
    	, Alignment		-- = (HAlign, VAlign)
    	, HAlign(Left', Center, Right')
    	, VAlign(Top, Baseline, Bottom)
    	, setTextAlignment	-- :: Alignment -> Draw Alignment
    	) where

    #if !X_DISPLAY_MISSING
    import qualified Graphics.X11.Xlib as X
    import Graphics.HGL.X11.Types
    import Control.Concurrent.MVar (readMVar, takeMVar, putMVar)
    #else
    import qualified Graphics.Win32 as Win32
    import Graphics.HGL.Win32.Types
    import Data.Bits
    #endif

    import Graphics.HGL.Units (Point, Size)
    import Graphics.HGL.Draw.Monad (Graphic, Draw)
    import Graphics.HGL.Internals.Draw (mkDraw)
    import Graphics.HGL.Internals.Types
    	(RGB(..), BkMode(..), Alignment, HAlign(..), VAlign(..))

    ----------------------------------------------------------------
    -- The Interface (SOE, p50)
    ----------------------------------------------------------------

    -- | Render a 'String' positioned relative to the specified 'Point'.
    text         :: Point -> String                  -> Graphic  -- filled

    #if !X_DISPLAY_MISSING
    -- | @'textInfo' s@ returns:
    --
    -- (1) The offset at which the string would be drawn according to the
    --     current text alignment (e.g., @('Center', 'Baseline')@ will result
    --     in an offset of (-width\/2,0))
    --
    -- (2) The size at which the text would be drawn using the current font.
    --
    textInfo :: String -> Draw (Point,Size)
    #endif

    -- | Set the foreground color for drawing text, returning the previous value.
    setTextColor     :: RGB           -> Draw RGB

    -- | Set the background color for drawing text, returning the previous value.
    -- The background color is ignored when the mode is 'Transparent'.
    setBkColor       :: RGB           -> Draw RGB

    -- | Set the background mode for drawing text, returning the previous value.
    setBkMode        :: BkMode        -> Draw BkMode

    -- | Set the alignment for drawing text, returning the previous value.
    setTextAlignment :: Alignment     -> Draw Alignment

    ----------------------------------------------------------------
    -- The Implementation
    ----------------------------------------------------------------

    #if !X_DISPLAY_MISSING

    text p s = mkDraw (\ dc -> do
      bs <- readMVar (ref_bits dc)
      let
        Font f  = font bs
        (halign, valign) = textAlignment bs

        width   = X.textWidth f s
        ascent  = X.ascentFromFontStruct f
        descent = X.descentFromFontStruct f

        x' = case halign of
             Left'  -> x
             Center -> x - width `div` 2
             Right' -> x - width + 1
        y' = case valign of
             Top      -> y + ascent
             Baseline -> y
             Bottom   -> y - descent + 1

      draw (bkMode bs) (disp dc) (drawable dc) (textGC dc) x' y' s
     )
     where
      X.Point x y = fromPoint p

      -- Win32's DeviceContext has a BkMode in it.  In X, we call two different
      -- routines depending on what mode we want.
      draw Transparent = X.drawString
      draw Opaque      = X.drawImageString

    textInfo s = mkDraw $ \ dc -> do
      bs <- readMVar (ref_bits dc)
      let
        Font f  = font bs
        (halign, valign) = textAlignment bs

        width   = X.textWidth f s
        ascent  = X.ascentFromFontStruct f
        descent = X.descentFromFontStruct f

        x1 = case halign of
              Left'  -> 0
              Center -> - width `div` 2
              Right' -> - width + 1
        y1 = case valign of
              Top      -> ascent
              Baseline -> 0
              Bottom   -> - descent + 1

        x2 = x1 + width
        y2 = y1 + ascent + descent

        (x1',x2') = (min x1 x2, max x1 x2)
        (y1',y2') = (min y1 y2, max y1 y2)

      return (toPoint (X.Point x1 y1), toSize (fromIntegral (x2'-x1'), fromIntegral (y2'-y1')))

    setTextColor     x = mkDraw $ \ dc -> do
      bs <- takeMVar (ref_bits dc)
      putMVar (ref_bits dc) bs{textColor=x}
      p <- lookupColor (disp dc) x
      X.setForeground (disp dc) (textGC dc) p
      return (textColor bs)

    setBkColor       x = mkDraw $ \ dc -> do
      bs <- takeMVar (ref_bits dc)
      putMVar (ref_bits dc) bs{bkColor=x}
      p <- lookupColor (disp dc) x
      X.setBackground (disp dc) (textGC dc) p
      return (bkColor bs)

    setBkMode        x = mkDraw $ \ dc -> do
      bs <- takeMVar (ref_bits dc)
      putMVar (ref_bits dc) bs{bkMode=x}
      return (bkMode bs)

    setTextAlignment x = mkDraw $ \ dc -> do
      bs <- takeMVar (ref_bits dc)
      putMVar (ref_bits dc) bs{textAlignment=x}
      return (textAlignment bs)

    #else /* X_DISPLAY_MISSING */

    type TextAlignment = Win32.TextAlignment

    fromAlignment :: Alignment -> TextAlignment
    fromAlignment (ha,va) = hAlign ha .|. vAlign va

    hAlign :: HAlign -> TextAlignment
    hAlign Left'    = Win32.tA_LEFT
    hAlign Center  	= Win32.tA_CENTER
    hAlign Right'   = Win32.tA_RIGHT

    vAlign :: VAlign -> TextAlignment
    vAlign Top     	= Win32.tA_TOP
    vAlign Baseline	= Win32.tA_BASELINE
    vAlign Bottom  	= Win32.tA_BOTTOM

    toAlignment :: TextAlignment -> Alignment
    toAlignment x = (toHAlign (x .&. hmask), toVAlign (x .&. vmask))

    toHAlign x
      | x == Win32.tA_LEFT   = Left'
      | x == Win32.tA_CENTER = Center
      | x == Win32.tA_RIGHT  = Right'
      | otherwise            = Center -- safe(?) default

    toVAlign x
      | x == Win32.tA_TOP      = Top
      | x == Win32.tA_BASELINE = Baseline
      | x == Win32.tA_BOTTOM   = Bottom
      | otherwise              = Baseline -- safe(?) default

    -- Win32 doesn't seem to provide the masks I need - these ought to work.
    hmask = Win32.tA_LEFT .|. Win32.tA_CENTER   .|. Win32.tA_RIGHT
    vmask = Win32.tA_TOP  .|. Win32.tA_BASELINE .|. Win32.tA_BOTTOM

    fromBkMode :: BkMode -> Win32.BackgroundMode
    fromBkMode Opaque      = Win32.oPAQUE
    fromBkMode Transparent = Win32.tRANSPARENT

    toBkMode :: Win32.BackgroundMode -> BkMode
    toBkMode x
      | x == Win32.oPAQUE      = Opaque
      | x == Win32.tRANSPARENT = Transparent

    -- ToDo: add an update mode for these constants
    -- (not required at the moment since we always specify exactly where
    -- the text is to go)
    -- tA_NOUPDATECP :: TextAlignment
    -- tA_UPDATECP   :: TextAlignment

    text (x,y) s = mkDraw $ \ hdc ->
      Win32.textOut hdc (fromDimension x) (fromDimension y) s

    setTextColor c = mkDraw (\hdc -> do
      c' <- Win32.setTextColor hdc (fromRGB c)
      return (toRGB c'))

    setBkColor c = mkDraw (\hdc -> do
      c' <- Win32.setBkColor hdc (fromRGB c)
      return (toRGB c'))

    setBkMode m = mkDraw (\hdc -> do
      m' <- Win32.setBkMode hdc (fromBkMode m)
      return (toBkMode m'))

    setTextAlignment new_alignment = mkDraw (\hdc -> do
      old <- Win32.setTextAlign hdc (fromAlignment new_alignment)
      return (toAlignment old)
      )

    #endif /* X_DISPLAY_MISSING */

    ----------------------------------------------------------------
    -- End
    ----------------------------------------------------------------