~alcinnz/fontconfig-pure

ref: 94860b2edf2154be90a772add719a54168bbaa08 fontconfig-pure/lib/Graphics/Text/Font/Choose/Internal/FFI.hs -rw-r--r-- 3.7 KiB
94860b2e — Adrian Cochrane Add docstrings everywhere! 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
-- | Utilities for writing language bindings transferring complex parameters.
-- Encoding & decoding parameters via MessagePack.
module Graphics.Text.Font.Choose.Internal.FFI(
        unpackWithErr, withMessageIO, withMessage, fromMessage, fromMessage0,
        fromMessageIO0, withCString', peekCString', withForeignPtr'
    ) where

import Data.MessagePack (MessagePack(fromObject), pack, unpack, Object(ObjectStr))
import Foreign.C.String (CString, withCString, peekCString)
import Foreign.Ptr (Ptr, nullPtr)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Storable (Storable(..))
import Foreign.Marshal.Alloc (alloca, free)
import Data.Tuple (swap)
import Graphics.Text.Font.Choose.Result (throwNull, FcException)
import Data.Maybe (fromJust)
import Text.Read (readMaybe)
import Control.Exception (throw)

import Data.ByteString.Unsafe (unsafeUseAsCStringLen, unsafePackMallocCStringLen)
import Data.ByteString.Lazy (toStrict, fromStrict, ByteString)
import qualified Data.Text as Txt
import System.IO.Unsafe (unsafePerformIO)

-- | Decode a MessagePack packet whilst throwing textually-specified exceptions.
unpackWithErr :: MessagePack a => ByteString -> Maybe a
unpackWithErr bs = case unpack bs of
    Just (ObjectStr err) |
        Just x <- (readMaybe $ Txt.unpack err :: Maybe FcException) -> throw x
    Just x -> fromObject x
    Nothing -> Nothing

-- | Encode data via MessagePack to pass to an impure C function.
withMessageIO :: MessagePack a => (CString -> Int -> IO b) -> a -> IO b
withMessageIO cb a = unsafeUseAsCStringLen (toStrict $ pack a) (uncurry cb)

-- | Encode data via MessagePack to pass to a pure C function.
withMessage :: MessagePack a => (CString -> Int -> b) -> a -> b
withMessage inner arg = unsafePerformIO $ withMessageIO (\x -> return . inner x) arg

-- | Decode data via MessagePack returned from a pure C function.
fromMessage :: MessagePack a => (Ptr Int -> CString) -> Maybe a
fromMessage inner = unpackWithErr $ fromStrict $ unsafePerformIO $ do
    unsafePackMallocCStringLen . swap =<< withPtr (throwNull . inner)

-- | Decode data via MessagePack returned from a pure C function,
-- throwing exceptions upon failed decodes.
fromMessage0 :: MessagePack a => (Ptr Int -> CString) -> a
fromMessage0 = fromJust . fromMessage

-- | Decode data via MessagePack returned from an impure C function.
fromMessageIO :: MessagePack a => (Ptr Int -> IO CString) -> IO (Maybe a)
fromMessageIO inner = do
    (a, b) <- withPtr $ \ptr -> do
        throwNull =<< inner ptr
    bs <- unsafePackMallocCStringLen (b, a)
    return $ unpackWithErr $ fromStrict bs

-- | Decode data via MessagePack returned from an impure C function,
-- throwing exceptions upon failed decodes.
fromMessageIO0 :: MessagePack a => (Ptr Int -> IO CString) -> IO a
fromMessageIO0 inner = fromJust <$> fromMessageIO inner

-- | Pass a string to a pure C function.
withCString' :: (CString -> a) -> String -> a
withCString' inner = unsafePerformIO . flip withCString (return . inner)

-- | Return a string from a pure C function
peekCString' :: CString -> String
peekCString' ptr | ptr /= nullPtr = unsafePerformIO $ do
    ret <- peekCString ptr
    free ptr
    return ret
  | otherwise = ""

-- | Unwrap a foreign pointer to pass to a pure C function.
withForeignPtr' :: (Ptr a -> b) -> ForeignPtr a -> b
withForeignPtr' inner arg = unsafePerformIO $ withForeignPtr arg $ return . inner

-- I don't want to pull in all of inline-c for JUST this util!
-- | Pass a transient pointer to an impure C function,
-- for its value to be returned alongside that functions' return value.
withPtr :: (Storable a) => (Ptr a -> IO b) -> IO (a, b)
withPtr f = do
  alloca $ \ptr -> do
    x <- f ptr
    y <- peek ptr
    return (y, x)