M src/Typograffiti/Atlas.hs => src/Typograffiti/Atlas.hs +57 -37
@@ 15,6 15,7 @@ module Typograffiti.Atlas where
import Control.Monad
import Control.Monad.Except (MonadError (..))
import Control.Monad.IO.Class
+import Data.Maybe (fromMaybe)
import Data.IntMap (IntMap)
import qualified Data.IntMap as IM
import Data.Vector.Unboxed (Vector)
@@ 25,8 26,6 @@ import Graphics.GL.Types
import FreeType.Core.Types as BM
import FreeType.Support.Bitmap as BM
import FreeType.Support.Bitmap.Internal as BM
---import Graphics.Rendering.FreeType.Internal.Bitmap as BM
---import Graphics.Rendering.FreeType.Internal.GlyphMetrics as GM
import Linear
import Typograffiti.GL
@@ 85,10 84,11 @@ spacing = 1
measure
:: FT_Face
-> Int
+ -> (FT_GlyphSlot -> FreeTypeIO ())
-> (IntMap AtlasMeasure, AtlasMeasure)
-> Char
-> FreeTypeIO (IntMap AtlasMeasure, AtlasMeasure)
-measure fce maxw (prev, am@AM{..}) char
+measure fce maxw glyphCb (prev, am@AM{..}) char
-- Skip chars that have already been measured
| fromEnum char `IM.member` prev = return (prev, am)
| otherwise = do
@@ 96,10 96,11 @@ measure fce maxw (prev, am@AM{..}) char
V2 w h = amWH
-- Load the char, replacing the glyph according to
-- https://www.freetype.org/freetype2/docs/tutorial/step1.html
- loadChar fce (fromIntegral $ fromEnum char) ft_LOAD_RENDER
+ loadChar fce (fromIntegral $ fromEnum char) ft_LOAD_DEFAULT
-- Get the glyph slot
fce' <- liftIO $ peek fce
let slot = frGlyph fce'
+ glyphCb slot
-- Get the bitmap
slot' <- liftIO $ peek slot
let bmp = gsrBitmap slot'
@@ 179,45 180,64 @@ allocAtlas
allocAtlas fontFilePath gs str = do
e <- liftIO $ runFreeType $ do
fce <- newFace fontFilePath
- case gs of
- GlyphSizeInPixels w h -> setPixelSizes fce w h
- GlyphSizeByChar (CharSize w h dpix dpiy) -> setCharSize fce w h dpix dpiy
-
- (amMap, am) <- foldM (measure fce 512) (mempty, emptyAM) str
-
- let V2 w h = amWH am
- xymap :: IntMap (V2 Int)
- xymap = amXY <$> amMap
-
- t <- liftIO $ do
- t <- allocAndActivateTex GL_TEXTURE0
- glPixelStorei GL_UNPACK_ALIGNMENT 1
- withCString (replicate (w * h) $ toEnum 0) $
- glTexImage2D GL_TEXTURE_2D 0 GL_RED (fromIntegral w) (fromIntegral h)
- 0 GL_RED GL_UNSIGNED_BYTE . castPtr
- return t
-
- lib <- getLibrary
- atlas <- foldM (texturize xymap) (emptyAtlas lib fce t) str
-
- glGenerateMipmap GL_TEXTURE_2D
- glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT
- glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT
- glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR
- glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR
- glBindTexture GL_TEXTURE_2D 0
- glPixelStorei GL_UNPACK_ALIGNMENT 4
- return
- atlas{ atlasTextureSize = V2 w h
- , atlasGlyphSize = gs
- , atlasFilePath = fontFilePath
- }
+ allocRichAtlas fontFilePath fce (Just gs) renderGlyph str
either
(throwError . TypograffitiErrorFreetype "cannot alloc atlas")
(return . fst)
e
+-- | Allocate a new 'Atlas'.
+-- When creating a new 'Atlas' you must pass all the characters that you
+-- might need during the life of the 'Atlas'. Character texturization only
+-- happens once.
+allocRichAtlas
+ :: String
+ -- ^ Key identifying this altered font.
+ -> FT_Face
+ -- ^ Raw FreeType2-loaded font.
+ -> Maybe GlyphSize
+ -- ^ Size of glyphs in this Atlas, callers may configure this externally.
+ -> (FT_GlyphSlot -> FreeTypeIO ())
+ -- ^ Callback for mutating each glyph loaded from the given font.
+ -> String
+ -- ^ The characters to include in this 'Atlas'.
+ -> FreeTypeIO Atlas
+allocRichAtlas key fce gs cb str = do
+ case gs of
+ Just (GlyphSizeInPixels w h) -> setPixelSizes fce w h
+ Just (GlyphSizeByChar (CharSize w h dpix dpiy)) -> setCharSize fce w h dpix dpiy
+ Nothing -> return ()
+
+ (amMap, am) <- foldM (measure fce 512 cb) (mempty, emptyAM) str
+
+ let V2 w h = amWH am
+ xymap :: IntMap (V2 Int)
+ xymap = amXY <$> amMap
+
+ t <- liftIO $ do
+ t <- allocAndActivateTex GL_TEXTURE0
+ glPixelStorei GL_UNPACK_ALIGNMENT 1
+ withCString (replicate (w * h) $ toEnum 0) $
+ glTexImage2D GL_TEXTURE_2D 0 GL_RED (fromIntegral w) (fromIntegral h)
+ 0 GL_RED GL_UNSIGNED_BYTE . castPtr
+ return t
+
+ lib <- getLibrary
+ atlas <- foldM (texturize xymap) (emptyAtlas lib fce t) str
+
+ glGenerateMipmap GL_TEXTURE_2D
+ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT
+ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT
+ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR
+ glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR
+ glBindTexture GL_TEXTURE_2D 0
+ glPixelStorei GL_UNPACK_ALIGNMENT 4
+ return
+ atlas{ atlasTextureSize = V2 w h
+ , atlasGlyphSize = fromMaybe (GlyphSizeInPixels 0 0) gs
+ , atlasFilePath = key
+ }
-- | Releases all resources associated with the given 'Atlas'.
freeAtlas :: MonadIO m => Atlas -> m ()
M src/Typograffiti/Store.hs => src/Typograffiti/Store.hs +101 -0
@@ 29,6 29,15 @@ import Typograffiti.Atlas
import Typograffiti.Cache
import Typograffiti.Glyph
+-- For font registration APIs
+import Typograffiti.Utils
+import FreeType.Support.Bitmap.Internal
+import FreeType.Support.Outline.Internal
+import FreeType.Support.Outline
+import FreeType.Core.Types
+import Data.Maybe (fromMaybe)
+import System.IO
+
-- | A pre-rendered bit of text, ready to display given
-- some post compilition transformations. Also contains
@@ 71,6 80,8 @@ getTextRendering
-> FilePath
-- ^ The path to the font to use
-- for rendering.
+ -- Or alternatively: the `key`
+ -- identifying a registered font.
-> GlyphSize
-- ^ The size of the font glyphs.
-> String
@@ 149,3 160,93 @@ allocFont store file sz = do
$ putTMVar mvar
$ s{ textRenderingDataFontMap = M.insert (file, sz) font fontmap }
return font
+
+registerFont
+ :: Layout t
+ => FontStore t
+ -> String
+ -> FT_Face
+ -> Maybe GlyphSize
+ -> (FT_GlyphSlot -> FreeTypeIO ())
+ -> FreeTypeIO (Font t)
+-- | Register an externally-loaded font under a given key (low-level API)
+-- Allows registering a callback for mutating glyphs prior
+-- to being composited into place on the GPU, which is
+-- responsible for ensuring Typograffiti has a bitmap to composite.
+registerFont store key fce sz cb = do
+ let mvar = unFontStore store
+ s <- liftIO $ atomically $ takeTMVar mvar
+ atlas <-
+ allocRichAtlas
+ key
+ fce
+ sz
+ cb
+ $ S.toList
+ $ textRenderingDataCharSet s
+ let fontmap = textRenderingDataFontMap s
+ font = Font
+ { fontAtlas = atlas
+ , fontWordCache = mempty
+ }
+ let sz' = atlasGlyphSize atlas
+ liftIO
+ $ atomically
+ $ putTMVar mvar
+ $ s{ textRenderingDataFontMap = M.insert (key, sz') font fontmap }
+ return font
+
+registerStyledFont
+ :: ( MonadIO m
+ , MonadError TypograffitiError m
+ , Layout t
+ )
+ => FontStore t
+ -> String
+ -- ^ Key by which to identify this styled font
+ -> FilePath
+ -- ^ Path to the raw fontfile
+ -> FT_Pos
+ -- ^ How much to embolden the font
+ -- Negative values lighten the font.
+ -> Maybe FT_Pos
+ -- ^ How much to embolden the font vertically, if different from horizontally.
+ -> FT_Fixed
+ -- ^ How much to slant the font, approximating italics.
+ -> GlyphSize
+ -- ^ The desired fontsize
+ -> m (Font t)
+-- | Registers font under the given key modified to approximate the desired boldness & obliqueness.
+-- Adds negligable CPU latency,
+-- but best results always come from giving the font designing full artistic control.
+-- Obliqueness isn't currently supported on bitmap fonts.
+registerStyledFont store key file weight vweight slant sz = do
+ e <- liftIO $ runFreeType $ do
+ lib <- getLibrary
+ fce <- newFace file
+ registerFont store key fce (Just sz) $ modifyGlyph lib
+
+ either
+ (throwError . TypograffitiErrorFreetype "cannot alloc atlas")
+ (return . fst)
+ e
+ where
+ modifyGlyph lib glyf = do
+ glyf' <- liftIO $ peek glyf
+ case gsrFormat glyf' of
+ FT_GLYPH_FORMAT_OUTLINE -> modifyOutline glyf
+ FT_GLYPH_FORMAT_BITMAP -> modifyBitmap lib glyf
+ x -> liftIO $ do
+ hPrint stderr "Unsupported glyph format:"
+ hPrint stderr x
+ modifyOutline glyf = do
+ let outline = gsrOutline' glyf
+ runIOErr "ft_Outline_EmboldenXY" $
+ ft_Outline_EmboldenXY' outline weight $ fromMaybe weight vweight
+ liftIO $ ft_Outline_Transform outline $ FT_Matrix 1 slant 0 1
+ renderGlyph glyf
+ modifyBitmap lib glyf = do
+ let bitmap = gsrBitmap' glyf
+ runIOErr "ft_Bitmap_Embolden" $
+ ft_Bitmap_Embolden' lib bitmap weight $ fromMaybe weight vweight
+ -- FreeType doesn't have a transform method on bitmaps.
M src/Typograffiti/Utils.hs => src/Typograffiti/Utils.hs +16 -0
@@ 4,6 4,7 @@ module Typograffiti.Utils (
module FT
, FreeTypeT
, FreeTypeIO
+ , runIOErr
, getAdvance
, getCharIndex
, getLibrary
@@ 12,6 13,7 @@ module Typograffiti.Utils (
-- , hasKerning
, loadChar
, loadGlyph
+ , renderGlyph
, newFace
, setCharSize
, setPixelSizes
@@ 22,6 24,8 @@ module Typograffiti.Utils (
, ft_LOAD_FORCE_AUTOHINT, ft_LOAD_CROP_BITMAP, ft_LOAD_PEDANTIC, ft_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
, ft_LOAD_NO_RECURSE, ft_LOAD_IGNORE_TRANSFORM, ft_LOAD_MONOCHROME, ft_LOAD_LINEAR_DESIGN
, ft_LOAD_NO_AUTOHINT, ft_LOAD_COLOR, ft_LOAD_COMPUTE_METRICS, ft_LOAD_BITMAP_METRICS_ONLY
+ , gsrOutline'
+ , gsrBitmap'
) where
import Control.Monad.IO.Class (MonadIO, liftIO)
@@ 31,9 35,11 @@ import Control.Monad (unless)
import FreeType.Core.Base as FT
import FreeType.Core.Base.Internal as FT
import FreeType.Core.Types as FT
+import FreeType.Support.Outline as FT
import Foreign as FT
import Foreign.C.String as FT
import Unsafe.Coerce
+import Foreign.Ptr (Ptr(..), plusPtr)
-- TODO: Tease out the correct way to handle errors.
-- They're kinda thrown all willy nilly.
@@ 112,6 118,9 @@ loadGlyph ff fg flags = runIOErr "ft_Load_Glyph" $ ft_Load_Glyph' ff fg flags
loadChar :: MonadIO m => FT_Face -> FT_ULong -> FT_Int32 -> FreeTypeT m ()
loadChar ff char flags = runIOErr "ft_Load_Char" $ ft_Load_Char' ff char flags
+renderGlyph :: MonadIO m => FT_GlyphSlot -> FreeTypeT m ()
+renderGlyph glyph = runIOErr "ft_Render_Glyph" $ ft_Render_Glyph' glyph 0
+
--hasKerning :: MonadIO m => FT_Face -> FreeTypeT m Bool
--hasKerning = liftIO . ft_HAS_KERNING
--ft_HAS_KERNING FT_HAS_KERNING = return True
@@ 160,3 169,10 @@ getAdvance slot = do
slot' <- liftIO $ peek slot
let FT_Vector vx vy = gsrAdvance slot'
return (fromIntegral vx, fromIntegral vy)
+
+-- Offsets taken from: https://hackage.haskell.org/package/freetype2-0.2.0/docs/src/FreeType.Circular.Types.html#line-372
+gsrOutline' :: FT_GlyphSlot -> Ptr FT_Outline
+gsrOutline' slot = plusPtr slot 200
+
+gsrBitmap' :: FT_GlyphSlot -> Ptr FT_Bitmap
+gsrBitmap' slot = plusPtr slot 152