gelatin-0.0.0.0: src/Gelatin/Core/Rendering/Font.hs
{-# 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