packages feed

monomer-1.4.0.0: src/Monomer/Graphics/FFI.chs

{-|
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 (useAsCString)
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

-- 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 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)