{-| Module : Monomer.Graphics.FFI Copyright : (c) 2018 Francisco Vallarino, (c) 2016 Moritz Kiefer License : BSD-3-Clause (see the LICENSE file) Maintainer : fjvallarino@gmail.com Stability : experimental Portability : non-portable Provides functions for getting text dimensions and metrics. Based on code from cocreature's https://github.com/cocreature/nanovg-hs -} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} module Monomer.Graphics.FFI where import Control.Monad (forM, (>=>)) import Data.ByteString (useAsCStringLen, useAsCString, ByteString) import Data.Text (Text) import Data.Text.Foreign (withCStringLen) import Data.Sequence (Seq) import Foreign import Foreign.C (CString) import Foreign.C.Types import Foreign.Marshal.Alloc import Foreign.Ptr import Foreign.Storable import qualified Data.Sequence as Seq import qualified Data.Text as T import qualified Data.Text.Encoding as T import Monomer.Graphics.Types (GlyphPos(..)) #include "fontmanager.h" -- | Vector of 4 strict elements. data V4 a = V4 !a !a !a !a deriving (Show, Read, Eq, Ord) -- | Bounds of a block of text. newtype Bounds = Bounds (V4 CFloat) deriving (Show, Read, Eq, Ord) instance Storable Bounds where sizeOf _ = sizeOf (0 :: CFloat) * 4 alignment _ = alignment (0 :: CFloat) peek p = do let p' = castPtr p :: Ptr CFloat a <- peekElemOff p' 0 b <- peekElemOff p' 1 c <- peekElemOff p' 2 d <- peekElemOff p' 3 pure (Bounds (V4 a b c d)) poke p (Bounds (V4 a b c d)) = do let p' = castPtr p :: Ptr CFloat pokeElemOff p' 0 a pokeElemOff p' 1 b pokeElemOff p' 2 c pokeElemOff p' 3 d -- | Position of a glyph in a text string. data GlyphPosition = GlyphPosition { -- | Pointer of the glyph in the input string. str :: !(Ptr CChar), -- | The x-coordinate of the logical glyph position. glyphX :: !CFloat, -- | The left bound of the glyph shape. glyphPosMinX :: !CFloat, -- | The right bound of the glyph shape. glyphPosMaxX :: !CFloat, -- | The lower bound of the glyph shape. glyphPosMinY :: !CFloat, -- | The upper bound of the glyph shape. glyphPosMaxY :: !CFloat } deriving (Show, Eq, Ord) instance Storable GlyphPosition where sizeOf _ = {# sizeof FMGglyphPosition #} alignment _ = {#alignof FMGglyphPosition#} peek p = do str <- {#get FMGglyphPosition->str#} p x <- {#get FMGglyphPosition->x#} p minx <- {#get FMGglyphPosition->minx#} p maxx <- {#get FMGglyphPosition->maxx#} p miny <- {#get FMGglyphPosition->miny#} p maxy <- {#get FMGglyphPosition->maxy#} p pure (GlyphPosition str x minx maxx miny maxy) poke p (GlyphPosition str x minx maxx miny maxy) = do {#set FMGglyphPosition->str#} p str {#set FMGglyphPosition->x#} p x {#set FMGglyphPosition->minx#} p minx {#set FMGglyphPosition->maxx#} p maxx {#set FMGglyphPosition->miny#} p miny {#set FMGglyphPosition->maxy#} p maxy {#pointer *FMGglyphPosition as GlyphPositionPtr -> GlyphPosition#} -- | Reads Bounds from a pointer. peekBounds :: Ptr CFloat -> IO Bounds peekBounds = peek . castPtr -- | Allocates space for Bounds. allocaBounds :: (Ptr CFloat -> IO b) -> IO b allocaBounds f = alloca (\(p :: Ptr Bounds) -> f (castPtr p)) withCString :: Text -> (CString -> IO b) -> IO b withCString t = useAsCString (T.encodeUtf8 t) withText :: Text -> (CString -> IO b) -> IO b withText t = useAsCString (T.encodeUtf8 t) -- | Marshalling helper for a constant 'nullPtr' withNull :: (Ptr a -> b) -> b withNull f = f nullPtr -- | Same as CStringLen, but for strings of unsigned char* array type. type CUStringLen = (Ptr CUChar, CInt) -- | Same as 'useAsCStringLen', but works with unsigned char* arrays. useAsCUStringLen :: ByteString -> (CUStringLen -> IO a) -> IO a useAsCUStringLen bs f = useAsCStringLen bs (\(ptr, len) -> f (castPtr ptr, fromIntegral len)) -- | Same as 'useAsCUStringLen', but copies the underlying memory, leaving freeing it to the C code. allocCUStringLen :: ByteString -> (CUStringLen -> IO a) -> IO a allocCUStringLen bs f = useAsCUStringLen bs (copyCUStringLenMemory >=> f) -- | Copy memory under given pointer to a new address. -- The allocated memory is not garbage-collected and needs to be freed manually later. copyCUStringLenMemory :: CUStringLen -> IO CUStringLen copyCUStringLenMemory (from, len) = let intLen = fromIntegral len in do to <- mallocBytes intLen copyBytes to from intLen return (to, len) -- Common {# pointer *FMcontext as FMContext newtype #} deriving instance Storable FMContext {# fun unsafe fmInit {`Double'} -> `FMContext' #} {# fun unsafe fmCreateFont {`FMContext', withCString*`Text', withCString*`Text'} -> `Int' #} {# fun unsafe fmCreateFontMem {`FMContext', withCString*`Text', allocCUStringLen*`ByteString'&} -> `Int' #} {# fun unsafe fmSetScale {`FMContext', `Double'} -> `()' #} {# fun unsafe fmFontFace {`FMContext', withCString*`Text'} -> `()' #} {# fun unsafe fmFontSize {`FMContext', `Double'} -> `()' #} {# fun unsafe fmFontBlur {`FMContext', `Double'} -> `()' #} {# fun unsafe fmTextLetterSpacing {`FMContext', `Double'} -> `()' #} {# fun unsafe fmTextLineHeight {`FMContext', `Double'} -> `()' #} {# fun unsafe fmTextMetrics as fmTextMetrics_ {`FMContext', alloca- `CFloat' peek*, alloca- `CFloat' peek*, alloca- `CFloat' peek*} -> `()' #} fmTextMetrics :: FMContext -> IO (Double, Double, Double) fmTextMetrics fm = do (asc, desc, lineh) <- fmTextMetrics_ fm return (realToFrac asc, realToFrac desc, realToFrac lineh) {# fun unsafe fmTextBounds as fmTextBounds_ {`FMContext', `Double', `Double', withText*`Text', withNull-`Ptr CUChar', allocaBounds-`Bounds'peekBounds*} -> `Double' #} fmTextBounds :: FMContext -> Double -> Double -> Text -> IO (Double, Double, Double, Double) fmTextBounds fm x y text = do (_, Bounds (V4 x1 y1 x2 y2)) <- fmTextBounds_ fm x y text return (realToFrac x1, realToFrac y1, realToFrac x2, realToFrac y2) {# fun unsafe fmTextGlyphPositions as fmTextGlyphPositions_ {`FMContext', `Double', `Double', id`Ptr CChar', id`Ptr CChar', `GlyphPositionPtr', `CInt'} -> `CInt' #} fmTextGlyphPositions :: FMContext -> Double -> Double -> Text -> IO (Seq GlyphPosition) fmTextGlyphPositions c x y text = withCStringLen text $ \(ptr, len) -> do let startPtr = ptr let endPtr = ptr `plusPtr` len allocaBytesAligned bufferSize align $ \arrayPtr -> do count <- fmTextGlyphPositions_ c x y startPtr endPtr arrayPtr maxGlyphs Seq.fromList <$> readChunk arrayPtr count where maxGlyphs = fromIntegral (T.length text) bufferSize = sizeOf (undefined :: GlyphPosition) * fromIntegral maxGlyphs align = alignment (undefined :: GlyphPosition) readChunk :: GlyphPositionPtr -> CInt -> IO [GlyphPosition] readChunk arrayPtr count = forM [0..count-1] $ \i -> peekElemOff arrayPtr (fromIntegral i)