{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -- | -- Module: Typograffiti.Monad -- Copyright: (c) 2018 Schell Scivally -- License: MIT -- Maintainer: Schell Scivally -- -- A storage context an ops for rendering text with multiple fonts -- and sizes, hiding the details of the Atlas and WordCache. module Typograffiti.Store where import Control.Concurrent.STM (TMVar, atomically, newTMVar, putTMVar, readTMVar, takeTMVar) import Control.Monad.Except (MonadError (..), liftEither) import Control.Monad.IO.Class (MonadIO (..)) import Data.Map (Map) import qualified Data.Map as M import Data.Set (Set) import qualified Data.Set as S import Linear import Typograffiti.Atlas import Typograffiti.Cache import Typograffiti.Glyph -- | A pre-rendered bit of text, ready to display given -- some post compilition transformations. Also contains -- the text size. data RenderedText t m = RenderedText { drawRenderedText :: t -> m () , sizeOfRenderedText :: V2 Int } data Font t = Font { fontAtlas :: Atlas , fontWordCache :: WordCache t } data TextRenderingData t = TextRenderingData { textRenderingDataAllocWord :: Atlas -> String -> IO (Either TypograffitiError (AllocatedRendering t)) -- ^ The operation used to alloc a word. -- Generate geometry, use a shader program, set uniforms, etc. , textRenderingDataFontMap :: Map (FilePath, GlyphSize) (Font t) -- ^ The cached fonts. , textRenderingDataCharSet :: Set Char -- ^ The character set to have available in all allocated Atlas types. } -- | Stored fonts at specific sizes. newtype FontStore t = FontStore { unFontStore :: TMVar (TextRenderingData t)} getTextRendering :: ( MonadIO m , MonadError TypograffitiError m , Layout t ) => FontStore t -- ^ The font store. -> FilePath -- ^ The path to the font to use -- for rendering. -> GlyphSize -- ^ The size of the font glyphs. -> String -- ^ The string to render. -> m (RenderedText t m) -- ^ The rendered text, ready to draw to the screen. getTextRendering store file sz str = do let mvar = unFontStore store s <- liftIO $ atomically $ readTMVar mvar font <- case M.lookup (file, sz) $ textRenderingDataFontMap s of Nothing -> allocFont store file sz Just font -> return font (draw, tsz, cache) <- loadText (\x y -> liftIO (textRenderingDataAllocWord s x y) >>= liftEither) (fontAtlas font) (fontWordCache font) str liftIO $ atomically $ do s1 <- takeTMVar mvar let alterf Nothing = Just $ Font (fontAtlas font) cache alterf (Just (Font atlas _)) = Just $ Font atlas cache fontmap = M.alter alterf (file,sz) $ textRenderingDataFontMap s1 putTMVar mvar s1{ textRenderingDataFontMap = fontmap } return RenderedText { drawRenderedText = liftIO . draw , sizeOfRenderedText = tsz } newDefaultFontStore :: ( MonadIO m , MonadError TypograffitiError m , Integral i ) => IO (V2 i) -> m (FontStore [TextTransform]) newDefaultFontStore getDims = do aw <- makeDefaultAllocateWord getDims let dat = TextRenderingData { textRenderingDataAllocWord = aw , textRenderingDataFontMap = mempty , textRenderingDataCharSet = S.fromList asciiChars } FontStore <$> liftIO (atomically $ newTMVar dat) allocFont :: ( MonadIO m , MonadError TypograffitiError m , Layout t ) => FontStore t -> FilePath -> GlyphSize -> m (Font t) allocFont store file sz = do let mvar = unFontStore store s <- liftIO $ atomically $ takeTMVar mvar atlas <- allocAtlas file sz $ S.toList $ textRenderingDataCharSet s let fontmap = textRenderingDataFontMap s font = Font { fontAtlas = atlas , fontWordCache = mempty } liftIO $ atomically $ putTMVar mvar $ s{ textRenderingDataFontMap = M.insert (file, sz) font fontmap } return font