brush-stroking-0.1.0.0: src/BrushStroking/Serialisable.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module BrushStroking.Serialisable
( Serialisable
-- * FromJSON (using hermes-json)
, FromJSON(..)
, key, keyOptional
, decodeSequence
, decodeCurve
, decodeCurves
, decodeSpline
)
where
-- base
import Data.Functor
( (<&>) )
import Data.IORef
( newIORef, atomicModifyIORef' )
import Data.Maybe
( fromMaybe )
import Unsafe.Coerce
( unsafeCoerce )
-- aeson
import Data.Aeson
( ToJSON(..), (.=) )
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Key as Aeson
-- containers
import Data.Sequence
( Seq )
import qualified Data.Sequence as Seq
( empty, fromList )
-- hermes-json
import qualified Data.Hermes as Hermes
-- text
import Data.Text
( Text )
-- transformers
import Control.Monad.IO.Class
( MonadIO(liftIO) )
-- meta-brushes
import Math.Bezier.Spline
( Spline(..), SplineType(..), SSplineType(..), SplineTypeI(..)
, Curves(..), Curve(..), NextPoint(..)
)
import Math.Linear
( ℝ(..), T(..)
, Fin(..), Representable(tabulate, index)
)
import BrushStroking.Records
--------------------------------------------------------------------------------
class ( Aeson.ToJSON a, FromJSON a ) => Serialisable a where
instance ( Aeson.ToJSON a, FromJSON a ) => Serialisable a where
class FromJSON a where
decoder :: Hermes.Decoder a
instance FromJSON Bool where
decoder = Hermes.bool
instance FromJSON Double where
decoder = Hermes.double
instance FromJSON Text where
decoder = Hermes.text
instance FromJSON a => FromJSON [a] where
decoder = Hermes.list decoder
key :: FromJSON a => Text -> Hermes.FieldsDecoder a
key k = Hermes.atKey k decoder
keyOptional :: FromJSON a => Text -> Hermes.FieldsDecoder (Maybe a)
keyOptional k = Hermes.atKeyOptional k decoder
instance MonadIO Hermes.Decoder where
liftIO a = unsafeCoerce $ \ ( _ :: Hermes.Object ) ( _ :: Hermes.HermesEnv ) -> a
instance MonadIO Hermes.FieldsDecoder where
liftIO a = unsafeCoerce $ \ ( _ :: Hermes.Object ) -> ( liftIO @Hermes.Decoder a )
instance Aeson.ToJSON ( ℝ 2 ) where
toJSON ( ℝ2 x y ) = Aeson.object [ "x" .= x, "y" .= y ]
toEncoding ( ℝ2 x y ) = Aeson.pairs ( "x" .= x <> "y" .= y )
instance FromJSON ( ℝ 2 ) where
decoder =
Hermes.object $
ℝ2 <$> key "x" <*> key "y"
deriving newtype instance Aeson.ToJSON ( T ( ℝ 2 ) )
instance FromJSON ( T ( ℝ 2 ) ) where
decoder = T <$> decoder @( ℝ 2 )
instance ( KnownSymbols ks, Representable Double ( ℝ ( Length ks ) ) )
=> Aeson.ToJSON ( Record ks ) where
toJSON r = Aeson.object $
zip [1..] ( knownSymbols @ks ) <&> \ ( i, fld ) ->
( Aeson.fromText fld .= index r ( Fin i ) )
instance ( KnownSymbols ks, Representable Double ( ℝ ( Length ks ) ) )
=> FromJSON ( Record ks ) where
decoder =
Hermes.object $
decodeFields <$>
traverse key ( knownSymbols @ks )
where
decodeFields :: [ Double ] -> Record ks
decodeFields coords =
MkR $ tabulate \ ( Fin i ) ->
coords !! ( fromIntegral i - 1 )
--------------------------------------------------------------------------------
instance ( SplineTypeI clo, Aeson.ToJSON ptData ) => Aeson.ToJSON ( Curve clo crvData ptData ) where
toJSON curve = Aeson.object $
case ssplineType @clo of
SOpen ->
case curve of
LineTo ( NextPoint p1 ) _ ->
[ "p1" .= p1 ]
Bezier2To p1 ( NextPoint p2 ) _ ->
[ "p1" .= p1, "p2" .= p2 ]
Bezier3To p1 p2 ( NextPoint p3 ) _ ->
[ "p1" .= p1, "p2" .= p2, "p3" .= p3 ]
SClosed ->
case curve of
LineTo BackToStart _ -> []
Bezier2To p1 BackToStart _ ->
[ "p1" .= p1 ]
Bezier3To p1 p2 BackToStart _ ->
[ "p1" .= p1, "p2" .= p2 ]
instance ( SplineTypeI clo, Aeson.ToJSON ptData ) => Aeson.ToJSON ( Curves clo crvData ptData ) where
toJSON curves = case ssplineType @clo of
SOpen -> toJSON ( openCurves curves )
SClosed ->
case curves of
NoCurves -> Aeson.object [ ]
ClosedCurves prevs lst ->
Aeson.object
[ "prevOpenCurves" .= prevs
, "lastClosedCurve" .= lst
]
instance ( SplineTypeI clo, Aeson.ToJSON ptData ) => Aeson.ToJSON ( Spline clo crvData ptData ) where
toJSON ( Spline { splineStart, splineCurves } ) =
Aeson.object
[ "splineStart" .= splineStart
, "splineCurves" .= splineCurves ]
decodeSequence :: Hermes.Decoder a -> Hermes.Decoder ( Seq a )
decodeSequence dec = Seq.fromList <$> Hermes.list dec
decodeCurve
:: forall clo ptData crvData
. ( SplineTypeI clo, FromJSON ptData )
=> Hermes.FieldsDecoder crvData
-> Hermes.Decoder ( Curve clo crvData ptData )
decodeCurve decodeCurveData = do
Hermes.object do
crvData <- decodeCurveData
case ssplineType @clo of
SOpen -> do
p1 <- key "p1"
mb_p2 <- keyOptional "p2"
case mb_p2 of
Nothing ->
pure $ LineTo ( NextPoint p1 ) crvData
Just p2 -> do
mb_p3 <- keyOptional "p3"
case mb_p3 of
Nothing -> pure $ Bezier2To p1 ( NextPoint p2 ) crvData
Just p3 -> pure $ Bezier3To p1 p2 ( NextPoint p3 ) crvData
SClosed -> do
mb_p1 <- keyOptional "p1"
case mb_p1 of
Nothing ->
pure $ LineTo BackToStart crvData
Just p1 -> do
mb_p2 <- keyOptional "p2"
case mb_p2 of
Nothing -> pure $ Bezier2To p1 BackToStart crvData
Just p2 -> pure $ Bezier3To p1 p2 BackToStart crvData
decodeCurves
:: forall clo ptData crvData
. ( SplineTypeI clo, FromJSON ptData )
=> Hermes.FieldsDecoder crvData
-> Hermes.Decoder ( Curves clo crvData ptData )
decodeCurves decodeCrvData = do
case ssplineType @clo of
SOpen -> do
OpenCurves <$> decodeSequence ( decodeCurve @Open decodeCrvData )
SClosed -> Hermes.object do
mbLastCurve <- Hermes.atKeyOptional "lastClosedCurve" ( decodeCurve @Closed decodeCrvData )
case mbLastCurve of
Nothing -> pure NoCurves
Just lastCurve -> do
prevCurves <- fromMaybe Seq.empty <$>
Hermes.atKeyOptional "prevOpenCurves" ( decodeSequence $ decodeCurve @Open decodeCrvData )
pure ( ClosedCurves prevCurves lastCurve )
decodeSpline
:: forall clo ptData crvData
. ( SplineTypeI clo, FromJSON ptData )
=> ( Integer -> Hermes.FieldsDecoder crvData )
-> Hermes.Decoder ( Spline clo crvData ptData )
decodeSpline newCurve = do
ref <- liftIO $ newIORef 0
let newCrvData :: Hermes.FieldsDecoder crvData
newCrvData = do
i <- liftIO $ atomicModifyIORef' ref ( \ o -> ( o + 1, o ) )
newCurve i
Hermes.object $ do
splineStart <- key "splineStart"
splineCurves <- Hermes.atKey "splineCurves" ( decodeCurves @clo newCrvData )
pure ( Spline { splineStart, splineCurves } )