module Graphics.Text.TrueType.FontFolders
( loadUnixFontFolderList
, loadWindowsFontFolderList
, fontFolders
, findFont
, descriptorOf
, FontCache( .. )
, FontDescriptor( .. )
, emptyFontCache
, buildFontCache
, enumerateFonts
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( (<*>), (<$>) )
#endif
#if !MIN_VERSION_base(4,6,0)
import Control.Monad( guard )
import Control.Exception( tryJust )
import System.IO.Error( isDoesNotExistError )
import System.Environment( getEnv )
#else
import System.Environment( lookupEnv )
#endif
import Control.Monad( when, replicateM )
import System.Directory( getDirectoryContents
, getHomeDirectory
, doesDirectoryExist
, doesFileExist
)
import qualified Data.ByteString as B
import Data.Binary( Binary( .. ) )
import Data.Binary.Get( Get
, getWord32be
, getByteString
)
import Data.Binary.Put( Put
, putWord32be
, putByteString )
import qualified Data.Map.Strict as M
import System.FilePath( (</>) )
import Text.XML.Light( elChildren
, elName
, onlyElems
, parseXML
, qName
, strContent )
import qualified Control.Exception as E
import qualified Data.Text as T
import qualified Data.Text.IO as T (readFile)
import Graphics.Text.TrueType.FontType
import Graphics.Text.TrueType.Header
import Graphics.Text.TrueType.Name
import Control.DeepSeq (($!!))
catchAny :: IO a -> (E.SomeException -> IO a) -> IO a
catchAny = E.catch
loadParseFontsConf :: IO [FilePath]
loadParseFontsConf = getPaths <$> T.readFile "/etc/fonts/fonts.conf"
where
getPaths s = map strContent
$ filter ((== "dir") . qName . elName)
$ concatMap elChildren
$ onlyElems
$ parseXML s
#if !MIN_VERSION_base(4,6,0)
lookupEnv :: String -> IO (Maybe String)
lookupEnv varName = do
v <- tryJust (guard . isDoesNotExistError) $ getEnv varName
case v of
Left _ -> return Nothing
Right val -> return $ Just val
#endif
loadUnixFontFolderList :: IO [FilePath]
loadUnixFontFolderList = catchAny
(do conf <- loadParseFontsConf
return $!! conf ++ map (</> "truetype") conf)
(const $ return defaults)
where
defaults = ["/usr/share/fonts", "/usr/local/share/fonts", "~/.fonts"]
loadWindowsFontFolderList :: IO [FilePath]
loadWindowsFontFolderList = toFontFolder <$> lookupEnv "Windir"
where toFontFolder (Just a) = [a </> "Fonts"]
toFontFolder Nothing = []
loadOsXFontFolderList :: IO [FilePath]
loadOsXFontFolderList = do
home <- getHomeDirectory
return [home </> "Library" </> "Fonts"
,"/" </> "Library" </> "Fonts"
,"/" </> "System" </> "Library" </> "Fonts"
,"/" </> "System Folder" </> "Fonts"
]
fontFolders :: IO [FilePath]
fontFolders = do
unix <- loadUnixFontFolderList
win <- loadWindowsFontFolderList
osx <- loadOsXFontFolderList
return $ unix ++ win ++ osx
data FontDescriptor = FontDescriptor
{
_descriptorFamilyName :: !T.Text
, _descriptorStyle :: !FontStyle
}
deriving (Eq, Ord, Show)
instance Binary FontDescriptor where
put (FontDescriptor t s) = put (T.unpack t) >> put s
get = FontDescriptor <$> (T.pack <$> get) <*> get
newtype FontCache =
FontCache (M.Map FontDescriptor FilePath)
deriving Show
emptyFontCache :: FontCache
emptyFontCache = FontCache M.empty
signature :: B.ByteString
signature = "FontyFruity__FONTCACHE:0.5"
putFontCache :: FontCache -> Put
putFontCache (FontCache cache) = do
putByteString signature
putWord32be . fromIntegral $ M.size cache
mapM_ put $ M.toList cache
getFontCache :: Get FontCache
getFontCache = do
str <- getByteString $ B.length signature
when (str /= signature) $
fail "Invalid font cache"
count <- fromIntegral <$> getWord32be
FontCache . M.fromList <$> replicateM count get
instance Binary FontCache where
put = putFontCache
get = getFontCache
enumerateFonts :: FontCache -> [FontDescriptor]
enumerateFonts (FontCache fs) = M.keys fs
descriptorOf :: Font -> Maybe FontDescriptor
descriptorOf font = do
hdr <- _fontHeader font
names <- _fontNames font
return $ FontDescriptor (fontFamilyName names) (_fHdrMacStyle hdr)
buildFontCache :: (FilePath -> IO (Maybe Font)) -> IO FontCache
buildFontCache loader = do
folders <- fontFolders
found <- build [("", v) | v <- folders]
return . FontCache
$ M.fromList [(d, path) | (Just d, path) <- found
, _descriptorFamilyName d /= ""]
where
build [] = return []
build ((".", _):rest) = build rest
build (("..", _):rest) = build rest
build ((_, n):rest) = do
isDirectory <- doesDirectoryExist n
if isDirectory then do
sub <- getDirectoryContents n
(++) <$> build [(s, n </> s) | s <- sub]
<*> build rest
else do
isFile <- doesFileExist n
if isFile then do
f <- loader n
case f of
Nothing -> build rest
Just fo -> ((descriptorOf fo, n) :) <$> build rest
else build rest
findFont :: (FilePath -> IO (Maybe Font)) -> String -> FontStyle
-> IO (Maybe FilePath)
findFont loader fontName fontStyle = do
folders <- fontFolders
searchIn [("", v) | v <- folders]
where
fontNameText = T.pack fontName
isMatching n (Font { _fontHeader = Just hdr
, _fontNames = Just names})
| _fHdrMacStyle hdr == fontStyle &&
fontFamilyName names == fontNameText = Just n
isMatching _ _ = Nothing
searchIn [] = return Nothing
searchIn ((".", _):rest) = searchIn rest
searchIn (("..", _):rest) = searchIn rest
searchIn ((_, n):rest) = do
isDirectory <- doesDirectoryExist n
let findOrRest Nothing = searchIn rest
findOrRest l = return l
if isDirectory then do
sub <- getDirectoryContents n
subRez <- searchIn [(s, n </> s) | s <- sub]
findOrRest subRez
else do
isFile <- doesFileExist n
if isFile then do
font <- loader n
findOrRest $ font >>= isMatching n
else searchIn rest