nanovg-0.5.2.0: test/NanoVGSpec.hs
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
module NanoVGSpec where
import Contexts
import Control.Applicative
import Control.Monad
import qualified Data.Map as M
import Data.Monoid
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable
import qualified Language.C.Inline as C
import qualified Language.C.Inline.Context as C
import qualified Language.C.Types as C
import NanoVG
import Test.Hspec
import Test.QuickCheck
C.context (C.baseCtx <> nanoVGCtx)
C.include "nanovg.h"
arbitraryCFloat :: Gen CFloat
arbitraryCFloat = realToFrac <$> (arbitrary :: Gen Float)
arbitraryPtr :: Gen (Ptr a)
arbitraryPtr = ((nullPtr `plusPtr`) . getNonNegative) <$> (arbitrary :: Gen (NonNegative Int))
instance Arbitrary Transformation where
arbitrary =
do a <- arbitraryCFloat
b <- arbitraryCFloat
c <- arbitraryCFloat
d <- arbitraryCFloat
e <- arbitraryCFloat
f <- arbitraryCFloat
pure (Transformation
(V2 (V3 a c e)
(V3 b d f)))
instance Arbitrary Extent where
arbitrary =
do a <- arbitraryCFloat
b <- arbitraryCFloat
pure (Extent (V2 a b))
instance Arbitrary Color where
arbitrary =
do r <- arbitraryCFloat
g <- arbitraryCFloat
b <- arbitraryCFloat
a <- arbitraryCFloat
pure (Color r g b a)
instance Arbitrary Paint where
arbitrary =
do xform <- arbitrary
extent <- arbitrary
radius <- arbitraryCFloat
feather <- arbitraryCFloat
innerColor <- arbitrary
outerColor <- arbitrary
image <- (Image . fromIntegral) <$> (arbitrary :: Gen Int)
pure (Paint xform extent radius feather innerColor outerColor image)
instance Arbitrary TextRow where
arbitrary =
do start <- arbitraryPtr
end <- arbitraryPtr
next <- arbitraryPtr
width <- arbitraryCFloat
minx <- arbitraryCFloat
maxx <- arbitraryCFloat
pure (TextRow start end next width minx maxx)
instance Arbitrary GlyphPosition where
arbitrary =
do str <- arbitraryPtr
x <- arbitraryCFloat
minx <- arbitraryCFloat
maxx <- arbitraryCFloat
pure (GlyphPosition str x minx maxx)
instance Arbitrary Bounds where
arbitrary = fmap Bounds $
V4 <$> arbitraryCFloat
<*> arbitraryCFloat
<*> arbitraryCFloat
<*> arbitraryCFloat
allocaElems :: forall a b. Storable a => Int -> (Ptr a -> IO b) -> IO b
allocaElems n = allocaBytesAligned (n * sizeOf (undefined :: a)) (alignment (undefined :: a))
pokeElems :: Storable a => Ptr a -> [a] -> IO ()
pokeElems ptr as = forM_ (zip [0 ..] as) $ \(i,a) -> pokeElemOff ptr i a
peekElems :: Storable a => Int -> Ptr a -> IO [a]
peekElems n ptr = forM [0..(n-1)] (peekElemOff ptr)
roundTripElems :: (Storable a, Eq a, Show a) => [a] -> (CInt -> Ptr b -> Ptr b -> IO ()) -> IO ()
roundTripElems as f = allocaElems n $ \inPtr ->
allocaElems n $ \outPtr -> do
pokeElems inPtr as
let cInPtr = castPtr inPtr
cOutPtr = castPtr outPtr
f (fromIntegral n) cInPtr cOutPtr
peekElems (fromIntegral n) outPtr `shouldReturn` as
where n = fromIntegral (length as)
roundTripTransformation :: [Transformation] -> IO ()
roundTripTransformation ts = do
roundTripElems ts $ \n cInPtr cOutPtr ->
[C.block| void {
float* in = $(float* cInPtr);
float* out = $(float* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
for (int j = 0; j < 6; ++j) {
out[6*i+j] = in[6*i+j];
}
}
}|]
roundTripExtents :: [Extent] -> IO ()
roundTripExtents es =
roundTripElems es $ \n cInPtr cOutPtr ->
[C.block| void {
float* in = $(float* cInPtr);
float* out = $(float* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
out[2*i] = in[2*i];
out[2*i+1] = in[2*i+1];
}
}|]
roundTripColors :: [Color] -> IO ()
roundTripColors cs =
roundTripElems cs $ \n cInPtr cOutPtr ->
[C.block| void {
NVGcolor* in = $(NVGcolor* cInPtr);
NVGcolor* out = $(NVGcolor* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
out[i].r = in[i].r;
out[i].g = in[i].g;
out[i].b = in[i].b;
out[i].a = in[i].a;
}
}|]
roundTripPaints :: [Paint] -> IO ()
roundTripPaints ps =
roundTripElems ps $ \n cInPtr cOutPtr ->
[C.block| void {
NVGpaint* in = $(NVGpaint* cInPtr);
NVGpaint* out = $(NVGpaint* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
for (int j = 0; j < 6; ++j) {
out[i].xform[j] = in[i].xform[j];
}
out[i].extent[0] = in[i].extent[0];
out[i].extent[1] = in[i].extent[1];
out[i].radius = in[i].radius;
out[i].feather = in[i].feather;
out[i].innerColor = in[i].innerColor;
out[i].outerColor = in[i].outerColor;
out[i].image = in[i].image;
}
}|]
roundTripGlyphPositions :: [GlyphPosition] -> IO ()
roundTripGlyphPositions ps =
roundTripElems ps $ \n cInPtr cOutPtr ->
[C.block| void {
NVGglyphPosition* in = $(NVGglyphPosition* cInPtr);
NVGglyphPosition* out = $(NVGglyphPosition* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
out[i].str = in[i].str;
out[i].x = in[i].x;
out[i].minx = in[i].minx;
out[i].maxx = in[i].maxx;
}
}|]
roundTripTextRows :: [TextRow] -> IO ()
roundTripTextRows rs =
roundTripElems rs $ \n cInPtr cOutPtr ->
[C.block| void {
NVGtextRow* in = $(NVGtextRow* cInPtr);
NVGtextRow* out = $(NVGtextRow* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
out[i].start = in[i].start;
out[i].end = in[i].end;
out[i].next = in[i].next;
out[i].width = in[i].width;
out[i].minx = in[i].minx;
out[i].maxx = in[i].maxx;
}
}|]
roundTripBounds :: [Bounds] -> IO ()
roundTripBounds bs =
roundTripElems bs $ \n cInPtr cOutPtr ->
[C.block| void {
float* in = $(float* cInPtr);
float* out = $(float* cOutPtr);
for (int i = 0; i < $(int n); ++i) {
for (int j = 0; j < 4; ++j) {
out[4*i+j] = in[4*i+j];
}
}
}|]
spec :: Spec
spec = do
describe "Storable Transformation" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripTransformation
describe "Storable Extent" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripExtents
describe "Storable Color" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripColors
describe "Storable Paint" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripPaints
describe "Storable GlyphPosition" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripGlyphPositions
describe "Storable TextRow" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripTextRows
describe "Storable Bounds" $ do
it "roundtrips via the storable instance" $ do
quickCheck roundTripBounds