{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE GADTs #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Gelatin.Core.Rendering.Font ( compileFontCache, fontGeom, findFont, allFonts, withFontAsync, withFont, concaveTriangles ) where import Gelatin.Core.Rendering.Types import Gelatin.Core.Rendering.Geometrical import Prelude hiding (init) import Control.Concurrent.Async import Linear import Graphics.Text.TrueType import qualified Data.Vector.Unboxed as UV compileFontCache :: IO (Async FontCache) compileFontCache = async $ do putStrLn "Loading font cache." a <- buildCache putStrLn "Font cache loaded." return a findFont :: Async FontCache -> FontDescriptor -> IO (Maybe FilePath) findFont afCache desc = do -- Get the font cache from our async container mfCache <- poll afCache -- If it has loaded check if the font in question exists return $ do efCache <- mfCache case efCache of Left _ -> Nothing Right cache -> findFontInCache cache desc allFonts :: Async FontCache -> IO (Maybe [FontDescriptor]) allFonts afcache = do mfcache <- poll afcache return $ do efcache <- mfcache case efcache of Left _ -> Nothing Right fcache -> Just $ enumerateFonts fcache withFontAsync :: Async FontCache -> FontDescriptor -> (Font -> IO a) -> IO (Maybe a) withFontAsync afcache desc f = do mPath <- findFont afcache desc case mPath of Nothing -> return Nothing Just path -> do ef <- loadFontFile path case ef of Left err -> putStrLn err >> return Nothing Right font -> Just `fmap` f font withFont :: FontCache -> FontDescriptor -> (Font -> IO a) -> IO (Maybe a) withFont cache desc f = do case findFontInCache cache desc of Nothing -> return Nothing Just fp -> do ef <- loadFontFile fp case ef of Left err -> putStrLn err >> return Nothing Right font -> Just `fmap` f font -------------------------------------------------------------------------------- -- Decomposition into triangles and beziers -------------------------------------------------------------------------------- -- | Ephemeral types for creating polygons from font outlines. -- Fonty gives us a [[Vector (Float, Float)]] for an entire string, which breaks down to type Contours = [Bezier (V2 Float)] -- Beziers type CharacterOutline = [Contours] type StringOutline = [CharacterOutline] -- | Merges poly a into poly b by "cutting" a and inserting b. --cutMerge :: Poly -> Poly -> Poly --cutMerge as bs = (take (ndx + 1) as) ++ bs ++ [head bs] ++ (drop ndx as) -- where (ndx, _) = head $ sortBy (\a b -> snd a `compare` snd b) $ -- zip [0..] $ map (`distance` (head bs)) as fontGeom :: Dpi -> FontString -> ([Bezier (V2 Float)], [Triangle (V2 Float)]) fontGeom dpi (FontString font px offset str) = let sz = pixelSizeInPointAtDpi px dpi cs = getStringCurveAtPoint dpi offset [(font, sz, str)] bs = beziers cs ts = concatMap (concatMap (concaveTriangles . onContourPoints)) bs in (concat $ concat bs,ts) fromFonty :: (UV.Unbox b1, Functor f1, Functor f) => ([V2 b1] -> b) -> f (f1 (UV.Vector (b1, b1))) -> f (f1 b) fromFonty f = fmap $ fmap $ f . UV.toList . UV.map (uncurry V2) beziers :: [[UV.Vector (Float, Float)]] -> StringOutline beziers = fromFonty (toBeziers . (fmap (fmap realToFrac))) -- | Turns a polygon into a list of triangles that can be rendered using the -- Concave Polygon Stencil Test -- @see http://www.glprogramming.com/red/chapter14.html#name13 concaveTriangles :: [a] -> [Triangle a] concaveTriangles [] = [] concaveTriangles (a:as) = tris a as where tris p (p':p'':ps) = Triangle p p' p'' : tris p (p'':ps) tris _ _ = [] -- | Collects the points that lie directly on the contour of the font -- outline. onContourPoints :: [Bezier a] -> [a] onContourPoints [] = [] onContourPoints ((Bezier LT a b c):bs) = [a,b,c] ++ onContourPoints bs onContourPoints ((Bezier _ a _ c):bs) = [a,c] ++ onContourPoints bs