-- | Store font glyphs in OpenGL textures -- -- TODO: add (optional) subpixel rendering support -- (pre-render say 4 versions of the same glyphs with fractional horizontal offsets) -- module Graphics.Rendering.MiniTypeset.FontTexture where -------------------------------------------------------------------------------- import Data.Word import Data.Char import GHC.Float import Foreign.Ptr import Foreign.Storable import Foreign.C import Foreign.Marshal.Array import Control.Monad import Control.Concurrent import Data.IORef import System.IO.Unsafe as Unsafe import Graphics.Rendering.OpenGL as GL import Graphics.Rendering.TrueType.STB -------------------------------------------------------------------------------- replacementCharacter :: Char replacementCharacter = '\xfffd' -------------------------------------------------------------------------------- -- newtype FontIdx = FontIdx { unFontIdx :: Int } -- | The location of a glyph in a font texture collection data BufLoc = BufLoc { _locGlyph :: !Glyph , _locBufIdx :: !Int -- ^ which buffer , _locBufXY :: !(Int,Int) -- ^ position within the texture (buffer) , _locBufSiz :: !(Int,Int) -- ^ size of the bounding box , _locBufOfs :: !(Int,Int) -- ^ offset relative to the bounding box , _locHM :: !(HorizontalMetrics Float) -- ^ for convenience, we cache the glyph metrics } deriving Show -- | A single texture buffer data TexBuf = TexBuf { _bufTexObj :: !TextureObject , _bufSize :: !(Int,Int) } deriving Show allocateTexBuf :: FontTexture -> IO TexBuf allocateTexBuf ftex = do let (w,h) = _ftexBufSize ftex texobj <- genObjectName let texbuf = TexBuf { _bufTexObj = texobj , _bufSize = (w,h) } textureBinding Texture2D $=! Just texobj textureLevelRange Texture2D $=! (0,0) textureFilter Texture2D $=! ((Nearest,Nothing),Nearest) textureWrapMode Texture2D S $=! (Repeated,ClampToBorder) textureWrapMode Texture2D T $=! (Repeated,ClampToBorder) textureWrapMode Texture2D R $=! (Repeated,ClampToBorder) textureBorderColor Texture2D $=! (Color4 0 0 0 0) generateMipmap Texture2D $= Disabled -- ????? {- -- uninitialized texture let pd = PixelData Alpha UnsignedByte nullPtr :: PixelData Word8 fi = fromIntegral :: Int -> GLint texImage2D Texture2D NoProxy 0 Alpha8 (TextureSize2D (fi w) (fi h)) 0 pd -} -- texture initialized with zeros let zeros = replicate (div (w*h+7) 8) (0::Word64) withArray zeros $ \ptr -> do let pd = PixelData Alpha UnsignedByte (castPtr ptr) :: PixelData Word8 fi = fromIntegral :: Int -> GLint texImage2D Texture2D NoProxy 0 Alpha8 (TextureSize2D (fi w) (fi h)) 0 pd {- -- texture initialized with pixel checkerboard (useful for debugging) let minta1 = concat $ replicate (div w 2) [ 0x40, 0xc0 :: Word8 ] minta2 = concat $ replicate (div w 2) [ 0xc0, 0x40 :: Word8 ] minta = concat $ concat $ replicate (div h 2) [ minta1 , minta2 ] withArray minta $ \ptr -> do let pd = PixelData Alpha UnsignedByte ptr :: PixelData Word8 fi = fromIntegral :: Int -> GLint texImage2D Texture2D NoProxy 0 Alpha8 (TextureSize2D (fi w) (fi h)) 0 pd -} --print (w,h,texobj) atomicModifyIORef' (_ftexBufs ftex) $ \oldList -> (oldList ++ [texbuf],()) return texbuf data TexCursor = TexCursor { _curBufIdx :: !Int -- ^ which buffer we are filling currently , _curX :: !Int -- ^ next X position , _curY :: !Int -- ^ current Y position , _cutMaxHt :: !Int -- ^ maximal bitmap height in this row } deriving Show zeroCursor :: TexCursor zeroCursor = TexCursor { _curBufIdx = 0 , _curX = 0 , _curY = 0 , _cutMaxHt = 0 } -- | A font texture collection: possibly several textures containing bitmaps -- a given font with a given height. data FontTexture = FontTexture { _ftexFont :: !Font , _ftexName :: !String , _ftexHeight :: !Float , _ftexScaling :: !Scaling , _ftexVM :: !(VerticalMetrics Float) , _ftexChars :: !(UnicodeCache (Maybe BufLoc)) , _ftexBufSize :: !(Int,Int) , _ftexCursor :: !(IORef TexCursor) , _ftexBufs :: !(IORef [TexBuf]) , _ftexLGapFactor :: !Double } instance Show FontTexture where show ftex = "FontTexture:<" ++ _ftexName ftex ++ ">" scaleU :: Float -> Unscaled -> Float scaleU s x = s * fromIntegral x defaultTextureSize :: (Int,Int) defaultTextureSize = (512,512) -- (128,128) -- (512,512) -- (1024,1024) newFontTexture :: Font -> Float -> String -> IO FontTexture newFontTexture font height name = newFontTexture' font height name defaultTextureSize 1 -- | Creates a new (empty) font texture newFontTexture' :: Font -> Float -> String -> (Int,Int) -> Double -> IO FontTexture newFontTexture' font height name size lgapfactor = do uc <- newUnicodeCache vmu <- getFontVerticalMetrics font let s = scaleForPixelHeight vmu height vms = fmap (scaleU s) vmu -- print vmu -- print vms cur <- newIORef zeroCursor texbufref <- newIORef [] let ftex = FontTexture { _ftexFont = font , _ftexName = name , _ftexHeight = height , _ftexScaling = (s,s) , _ftexVM = vms , _ftexChars = uc , _ftexBufSize = size , _ftexCursor = cur , _ftexBufs = texbufref , _ftexLGapFactor = lgapfactor } void $ allocateTexBuf ftex -- allocate the very first buffer return ftex -- | Finds a character in the font texture (rendering it first if necessary). -- If the glyph is not present in the font, we return the \"replacement character\" -- instead. If that doesn't exist either, then we return the \"not defined glyph\" -- (glyph #0) lookupFontTexture :: FontTexture -> Char -> IO BufLoc lookupFontTexture ftex ch = do mb <- mbLookupFontTexture ftex ch case mb of Just bufloc -> return bufloc Nothing -> do mb <- mbLookupFontTexture ftex replacementCharacter case mb of Just bufloc -> return bufloc Nothing -> lookupFontTexture ftex notDefinedGlyphChar -- | Finds a character in the font texture (rendering it first if necessary) mbLookupFontTexture :: FontTexture -> Char -> IO (Maybe BufLoc) mbLookupFontTexture ftex ch = lookupUnicodeCache ch allocate (_ftexChars ftex) where font = _ftexFont ftex (texw,texh) = _ftexBufSize ftex scaling@(hs,vs) = _ftexScaling ftex allocate :: Char -> IO (Maybe BufLoc) allocate ch = do mbglyph <- findGlyph font ch -- this is cached case mbglyph of Nothing -> return Nothing Just glyph -> do mb <- getGlyphBoundingBox font glyph case mb of Nothing -> return Nothing Just {} -> do (bm,bmofs) <- newGlyphBitmap font glyph scaling let (w,h) = bitmapSize bm oldCur@(TexCursor bufidx x y maxh) <- readIORef (_ftexCursor ftex) -- figure out render location and next cursor let x' = x + w + 2 y' = y + maxh + 2 w' = w + 2 -- we leave a 1 pixel (texel) border just to be safe h' = h + 2 (rloc,newCur) <- if x' < texw && y' < texh then return ( (bufidx,x,y) , TexCursor bufidx x' y (max h maxh) ) else if y' + h' < texh then return ( (bufidx,0,y') , TexCursor bufidx w' y' h ) else do void $ allocateTexBuf ftex let bufidx' = bufidx + 1 return ( (bufidx',0,0) , TexCursor bufidx' w' 0 h ) writeIORef (_ftexCursor ftex) newCur -- print (oldCur,newCur) oldAlign <- get (rowAlignment Unpack) rowAlignment Unpack $=! (1 :: GLint) -- render character at render location rloc let (rbuf,rx,ry) = rloc bufs <- readIORef (_ftexBufs ftex) let texBuf@(TexBuf texobj texsiz) = bufs!!rbuf withBitmap bm $ \_ _ ptr -> do let pd = PixelData Alpha UnsignedByte ptr fi = fromIntegral :: Int -> GLint textureBinding Texture2D $=! Just texobj texSubImage2D Texture2D 0 (TexturePosition2D (fi rx + 1) (fi ry + 1)) (TextureSize2D (fi w ) (fi h )) pd rowAlignment Unpack $=! oldAlign -- return location hmu <- getGlyphHorizontalMetrics font glyph let hms = fmap (scaleU hs) hmu let bufloc = BufLoc { _locGlyph = glyph , _locBufIdx = rbuf , _locBufXY = (rx,ry) , _locBufSiz = (w,h) , _locBufOfs = bmofs , _locHM = hms } -- putStrLn $ "'" ++ [ch] ++ "' -> " -- print bufloc return $ Just bufloc -------------------------------------------------------------------------------- -- | Renders the given font texture as a quad (useful for debugging) testRenderFullTextureQuad :: FontTexture -> Int -> IO () testRenderFullTextureQuad ftex idx = do bufs <- readIORef (_ftexBufs ftex) let buf = bufs!!idx matrixMode $= Projection loadIdentity ortho 0 1 1 0 (-1) 1 texture Texture2D $= Enabled textureBinding Texture2D $= Just (_bufTexObj buf) textureFunction $= Modulate blend $= Enabled blendFunc $= (SrcAlpha,OneMinusSrcAlpha) color (Color4 1 1 1 (1::Double)) let a = 0 :: Double b = 1 :: Double c = 0.1 :: Double d = 0.9 :: Double renderPrimitive Quads $ do texCoord (TexCoord2 a a) ; vertex (Vertex2 c c) texCoord (TexCoord2 a b) ; vertex (Vertex2 c d) texCoord (TexCoord2 b b) ; vertex (Vertex2 d d) texCoord (TexCoord2 b a) ; vertex (Vertex2 d c) texture Texture2D $= Disabled --------------------------------------------------------------------------------