{-# LANGUAGE OverloadedStrings #-} module Graphics.Text.Font.Choose.FontSet where import Graphics.Text.Font.Choose.Pattern import Graphics.Text.Font.Choose.Result (throwFalse, throwNull) import Foreign.Ptr (Ptr, castPtr, nullPtr) import Foreign.Storable (pokeElemOff, sizeOf) import Foreign.Marshal.Alloc (allocaBytes) import Foreign.Marshal.Array (peekArray) import Control.Monad (forM) import Control.Exception (bracket) -- For CSS bindings import Stylist.Parse (StyleSheet(..), parseProperties) import Data.CSS.Syntax.Tokens (Token(..)) import Data.Text (unpack, Text) import Graphics.Text.Font.Choose.Range (iRange) type FontSet = [Pattern] ------ --- Low-level ------ data FontSet' type FontSet_ = Ptr FontSet' withNewFontSet :: (FontSet_ -> IO a) -> IO a withNewFontSet = bracket fcFontSetCreate fcFontSetDestroy foreign import ccall "FcFontSetCreate" fcFontSetCreate :: IO FontSet_ foreign import ccall "FcFontSetDestroy" fcFontSetDestroy :: FontSet_ -> IO () withFontSet :: FontSet -> (FontSet_ -> IO a) -> IO a withFontSet fonts cb = withNewFontSet $ \fonts' -> do forM fonts $ \font -> do font' <- patternAsPointer font throwFalse <$> fcFontSetAdd fonts' font' cb fonts' foreign import ccall "FcFontSetAdd" fcFontSetAdd :: FontSet_ -> Pattern_ -> IO Bool withFontSets :: [FontSet] -> (Ptr FontSet_ -> Int -> IO a) -> IO a withFontSets fontss cb = let n = length fontss in allocaBytes (sizeOf (undefined :: FontSet_) * n) $ \fontss' -> withFontSets' fontss 0 fontss' $ cb fontss' n withFontSets' :: [FontSet] -> Int -> Ptr FontSet_ -> IO a -> IO a withFontSets' [] _ _ cb = cb withFontSets' (fonts:fontss) i fontss' cb = withFontSet fonts $ \fonts' -> do pokeElemOff fontss' i fonts' withFontSets' fontss (succ i) fontss' cb thawFontSet :: FontSet_ -> IO FontSet thawFontSet fonts' = do n <- get_fontSet_nfont fonts' array <- get_fontSet_fonts fonts' if n == 0 || array == nullPtr then return [] else do list <- peekArray n array forM list (thawPattern . throwNull) foreign import ccall "get_fontSet_nfont" get_fontSet_nfont :: FontSet_ -> IO Int foreign import ccall "get_fontSet_fonts" get_fontSet_fonts :: FontSet_ -> IO (Ptr Pattern_) thawFontSet_ :: IO FontSet_ -> IO FontSet thawFontSet_ cb = bracket (throwNull <$> cb) fcFontSetDestroy thawFontSet ------ --- CSS Bindings ------ data FontFaceParser a = FontFaceParser { cssFonts :: FontSet, cssInner :: a} properties2font :: [(Text, [Token])] -> Pattern properties2font (("font-family", [String font]):props) = setValue "family" Strong (unpack font) $ properties2font props properties2font (("font-family", [Ident font]):props) = setValue "family" Strong (unpack font) $ properties2font props properties2font (("font-stretch", [tok]):props) | Just x <- parseFontStretch tok = setValue "width" Strong x $ properties2font props properties2font (("font-stretch", [start, end]):props) | Just x <- parseFontStretch start, Just y <- parseFontStretch end = setValue "width" Strong (x `iRange` y) $ properties2font props properties2font (("font-weight", [tok]):props) | Just x <- parseFontWeight tok = setValue "width" Strong x $ properties2font props properties2font (("font-weight", [start, end]):props) | Just x <- parseFontStretch start, Just y <- parseFontStretch end = setValue "weight" Strong (x `iRange` y) $ properties2font props properties2font (_:props) = properties2font props properties2font [] = [] instance StyleSheet a => StyleSheet (FontFaceParser a) where setPriorities v (FontFaceParser x self) = FontFaceParser x $ setPriorities v self addRule (FontFaceParser x self) rule = FontFaceParser x $ addRule self rule addAtRule (FontFaceParser fonts self) "font-face" toks = let ((props, _), toks') = parseProperties toks in (FontFaceParser (properties2font props:fonts) self, toks') addAtRule (FontFaceParser x self) key toks = let (a, b) = addAtRule self key toks in (FontFaceParser x a, b)