blank-canvas-0.5: Graphics/Blank/JavaScript.hs
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}
module Graphics.Blank.JavaScript where
import Control.Applicative
import Data.Char (isControl, isAscii, ord)
import Data.Colour
import Data.Colour.SRGB
import Data.Default.Class
import Data.Ix
import Data.List
import Data.String
import Data.Text (Text, unpack)
import Data.Word (Word8)
import qualified Data.Vector.Unboxed as V
import Data.Vector.Unboxed (Vector)
import Numeric
import Text.ParserCombinators.ReadP (skipSpaces, string)
import Text.ParserCombinators.ReadPrec
import Text.Read
-------------------------------------------------------------
-- TODO: close off
class Image a where
jsImage :: a -> String
width :: Num b => a -> b
height :: Num b => a -> b
instance Image CanvasImage where
jsImage = jsCanvasImage
width (CanvasImage _ w _) = fromIntegral w
height (CanvasImage _ _ h) = fromIntegral h
-- The Image of a canvas is the the canvas context, not the DOM entry, so
-- you need to indirect back to the DOM here.
instance Image CanvasContext where
jsImage = (++ ".canvas") . jsCanvasContext
width (CanvasContext _ w _) = fromIntegral w
height (CanvasContext _ _ h) = fromIntegral h
-- instance Element Video -- Not supported
-----------------------------------------------------------------------------
-- TODO: close off
class Style a where
jsStyle :: a -> String
instance Style Text where { jsStyle = jsText }
instance Style CanvasGradient where { jsStyle = jsCanvasGradient }
instance Style CanvasPattern where { jsStyle = jsCanvasPattern }
instance Style (Colour Double) where { jsStyle = jsColour }
instance Style (AlphaColour Double) where { jsStyle = jsAlphaColour }
class Style a => CanvasColor a
jsCanvasColor :: CanvasColor color => color -> String
jsCanvasColor = jsStyle
instance CanvasColor Text
instance CanvasColor (Colour Double)
instance CanvasColor (AlphaColour Double)
-------------------------------------------------------------
-- | A handle to an offscreen canvas. CanvasContext can not be destroyed.
data CanvasContext = CanvasContext Int Int Int
deriving (Show,Eq,Ord)
-- | A handle to the Image. CanvasImages can not be destroyed.
data CanvasImage = CanvasImage Int Int Int deriving (Show,Eq,Ord)
-- | A handle to the CanvasGradient. CanvasGradients can not be destroyed.
newtype CanvasGradient = CanvasGradient Int deriving (Show,Eq,Ord)
-- | A handle to the CanvasPattern. CanvasPatterns can not be destroyed.
newtype CanvasPattern = CanvasPattern Int deriving (Show,Eq,Ord)
-------------------------------------------------------------
-- | The direction in which a 'CanvasPattern' repeats.
data RepeatDirection = Repeat -- ^ The pattern repeats both horizontally
-- and vertically.
| RepeatX -- ^ The pattern repeats only horizontally.
| RepeatY -- ^ The pattern repeats only vertically.
| NoRepeat -- ^ The pattern displays only once and
-- does not repeat.
deriving Eq
instance Default RepeatDirection where
def = Repeat
instance IsString RepeatDirection where
fromString = read
instance Read RepeatDirection where
readPrec = parens . lift $ do
skipSpaces
(string "repeat" >> return Repeat)
<|> (string "repeat-x" >> return RepeatX)
<|> (string "repeat-y" >> return RepeatY)
<|> (string "no-repeat" >> return NoRepeat)
instance Show RepeatDirection where
showsPrec _ rd = showString $ case rd of
Repeat -> "repeat"
RepeatX -> "repeat-x"
RepeatY -> "repeat-y"
NoRepeat -> "no-repeat"
-- | The style of the caps on the endpoints of a line.
data LineEndCap = ButtCap -- ^ Flat edges
| RoundCap -- ^ Semicircular end caps
| SquareCap -- ^ Square end caps
deriving Eq
instance Default LineEndCap where
def = ButtCap
instance IsString LineEndCap where
fromString = read
instance Read LineEndCap where
readPrec = parens $ do
Ident s <- lexP
case s of
"butt" -> return ButtCap
"round" -> return RoundCap
"square" -> return SquareCap
_ -> pfail
instance Show LineEndCap where
showsPrec _ le = showString $ case le of
ButtCap -> "butt"
RoundCap -> "round"
SquareCap -> "square"
-- | The style of corner that is created when two lines join.
data LineJoinCorner = BevelCorner -- ^ A filled triangle with a beveled edge
-- connects two lines.
| RoundCorner -- ^ A filled arc connects two lines.
| MiterCorner -- ^ A filled triangle with a sharp edge
-- connects two lines.
deriving Eq
instance Default LineJoinCorner where
def = MiterCorner
instance IsString LineJoinCorner where
fromString = read
instance Read LineJoinCorner where
readPrec = parens $ do
Ident s <- lexP
case s of
"bevel" -> return BevelCorner
"round" -> return RoundCorner
"miter" -> return MiterCorner
_ -> pfail
instance Show LineJoinCorner where
showsPrec _ corner = showString $ case corner of
BevelCorner -> "bevel"
RoundCorner -> "round"
MiterCorner -> "miter"
-- | The anchor point for text in the current 'DeviceContext'.
data TextAnchorAlignment = StartAnchor -- ^ The text is anchored at either its left edge
-- (if the canvas is left-to-right) or its right
-- edge (if the canvas is right-to-left).
| EndAnchor -- ^ The text is anchored at either its right edge
-- (if the canvas is left-to-right) or its left
-- edge (if the canvas is right-to-left).
| CenterAnchor -- ^ The text is anchored in its center.
| LeftAnchor -- ^ The text is anchored at its left edge.
| RightAnchor -- ^ the text is anchored at its right edge.
deriving Eq
instance Default TextAnchorAlignment where
def = StartAnchor
instance IsString TextAnchorAlignment where
fromString = read
instance Read TextAnchorAlignment where
readPrec = parens $ do
Ident s <- lexP
case s of
"start" -> return StartAnchor
"end" -> return EndAnchor
"center" -> return CenterAnchor
"left" -> return LeftAnchor
"right" -> return RightAnchor
_ -> pfail
instance Show TextAnchorAlignment where
showsPrec _ align = showString $ case align of
StartAnchor -> "start"
EndAnchor -> "end"
CenterAnchor -> "center"
LeftAnchor -> "left"
RightAnchor -> "right"
-- | The baseline alignment used when drawing text in the current 'DeviceContext'.
-- The baselines are ordered from highest ('Top') to lowest ('Bottom').
data TextBaselineAlignment = TopBaseline
| HangingBaseline
| MiddleBaseline
| AlphabeticBaseline
| IdeographicBaseline
| BottomBaseline
deriving (Bounded, Eq, Ix, Ord)
instance Default TextBaselineAlignment where
def = AlphabeticBaseline
instance IsString TextBaselineAlignment where
fromString = read
instance Read TextBaselineAlignment where
readPrec = parens $ do
Ident s <- lexP
case s of
"top" -> return TopBaseline
"hanging" -> return HangingBaseline
"middle" -> return MiddleBaseline
"alphabetic" -> return AlphabeticBaseline
"ideographic" -> return IdeographicBaseline
"bottom" -> return BottomBaseline
_ -> pfail
instance Show TextBaselineAlignment where
showsPrec _ bl = showString $ case bl of
TopBaseline -> "top"
HangingBaseline -> "hanging"
MiddleBaseline -> "middle"
AlphabeticBaseline -> "alphabetic"
IdeographicBaseline -> "ideographic"
BottomBaseline -> "bottom"
-------------------------------------------------------------
-- | 'ImageData' is a transliteration of the JavaScript ImageData,
-- There are two 'Int's, and one (unboxed) 'Vector' of 'Word8's.
-- width, height, data can be projected from 'ImageData',
-- 'Vector.length' can be used to find the length.
--
-- Note: 'ImageData' lives on the server, not the client.
data ImageData = ImageData !Int !Int !(Vector Word8) deriving (Show, Eq, Ord)
-------------------------------------------------------------
class JSArg a where
showJS :: a -> String
instance JSArg (AlphaColour Double) where
showJS aCol
| a >= 1 = jsColour rgbCol
| a <= 0 = jsLiteralString "rgba(0,0,0,0)"
| otherwise = jsLiteralString $ "rgba("
++ show r ++ ","
++ show g ++ ","
++ show b ++ ","
++ jsDouble a ++ ")"
where
a = alphaChannel aCol
rgbCol = darken (recip a) $ aCol `over` black
RGB r g b = toSRGB24 rgbCol
jsAlphaColour :: AlphaColour Double -> String
jsAlphaColour = showJS
instance JSArg Bool where
showJS True = "true"
showJS False = "false"
jsBool :: Bool -> String
jsBool = showJS
instance JSArg CanvasContext where
showJS (CanvasContext n _ _) = "canvasbuffers[" ++ show n ++ "]"
jsCanvasContext :: CanvasContext -> String
jsCanvasContext = showJS
instance JSArg CanvasImage where
showJS (CanvasImage n _ _) = "images[" ++ show n ++ "]"
jsCanvasImage :: CanvasImage -> String
jsCanvasImage = showJS
instance JSArg CanvasGradient where
showJS (CanvasGradient n) = "gradients[" ++ show n ++ "]"
jsCanvasGradient :: CanvasGradient -> String
jsCanvasGradient = showJS
instance JSArg CanvasPattern where
showJS (CanvasPattern n) = "patterns[" ++ show n ++ "]"
jsCanvasPattern :: CanvasPattern -> String
jsCanvasPattern = showJS
instance JSArg (Colour Double) where
showJS = jsLiteralString . sRGB24show
jsColour :: Colour Double -> String
jsColour = showJS
instance JSArg Double where
showJS a = showFFloat (Just 3) a ""
jsDouble :: Double -> String
jsDouble = showJS
instance JSArg ImageData where
showJS (ImageData w h d) = "ImageData(" ++ show w ++ "," ++ show h ++ ",[" ++ vs ++ "])"
where
vs = jsList show $ V.toList d
jsImageData :: ImageData -> String
jsImageData = showJS
instance JSArg Int where
showJS a = show a
instance JSArg LineEndCap where
showJS = jsLiteralString . show
jsLineEndCap :: LineEndCap -> String
jsLineEndCap = showJS
instance JSArg LineJoinCorner where
showJS = jsLiteralString . show
jsLineJoinCorner :: LineJoinCorner -> String
jsLineJoinCorner = showJS
jsList :: (a -> String) -> [a] -> String
jsList js = concat . intersperse "," . map js
instance JSArg RepeatDirection where
showJS = jsLiteralString . show
jsRepeatDirection :: RepeatDirection -> String
jsRepeatDirection = showJS
instance JSArg Text where
showJS = jsLiteralString . unpack
jsText :: Text -> String
jsText = showJS
instance JSArg TextAnchorAlignment where
showJS = jsLiteralString . show
jsTextAnchorAlignment :: TextAnchorAlignment -> String
jsTextAnchorAlignment = showJS
instance JSArg TextBaselineAlignment where
showJS = jsLiteralString . show
jsTextBaselineAlignment :: TextBaselineAlignment -> String
jsTextBaselineAlignment = showJS
-- The following was from our Sunroof compiler.
-- -------------------------------------------------------------
-- String Conversion Utilities: Haskell -> JS
-- -------------------------------------------------------------
-- | Transform a Haskell string into a string representing a JS string literal.
jsLiteralString :: String -> String
jsLiteralString = jsQuoteString . jsEscapeString
-- | Add quotes to a string.
jsQuoteString :: String -> String
jsQuoteString s = "\"" ++ s ++ "\""
-- | Transform a character to a string that represents its JS
-- unicode escape sequence.
jsUnicodeChar :: Char -> String
jsUnicodeChar c =
let hex = showHex (ord c) ""
in ('\\':'u': replicate (4 - length hex) '0') ++ hex
-- | Correctly replace Haskell characters by the JS escape sequences.
jsEscapeString :: String -> String
jsEscapeString [] = []
jsEscapeString (c:cs) = case c of
-- Backslash has to remain backslash in JS.
'\\' -> '\\' : '\\' : jsEscapeString cs
-- Special control sequences.
'\0' -> jsUnicodeChar '\0' ++ jsEscapeString cs -- Ambigous with numbers
'\a' -> jsUnicodeChar '\a' ++ jsEscapeString cs -- Non JS
'\b' -> '\\' : 'b' : jsEscapeString cs
'\f' -> '\\' : 'f' : jsEscapeString cs
'\n' -> '\\' : 'n' : jsEscapeString cs
'\r' -> '\\' : 'r' : jsEscapeString cs
'\t' -> '\\' : 't' : jsEscapeString cs
'\v' -> '\\' : 'v' : jsEscapeString cs
'\"' -> '\\' : '\"' : jsEscapeString cs
'\'' -> '\\' : '\'' : jsEscapeString cs
-- Non-control ASCII characters can remain as they are.
c' | not (isControl c') && isAscii c' -> c' : jsEscapeString cs
-- All other non ASCII signs are escaped to unicode.
c' -> jsUnicodeChar c' ++ jsEscapeString cs