typograffiti-0.2.0.0: src/Typograffiti/Cache.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
-- |
-- Module: Typograffiti.Cache
-- Copyright: (c) 2018 Schell Scivally, 2023 Adrian Cochrane
-- License: MIT
-- Maintainer: Schell Scivally <schell@takt.com>
-- & Adrian Cochrane <alcinnz@argonaut-constellation.org>
--
-- This module provides a method of caching rendererd text, making it suitable
-- for interactive rendering. You can use the defaultCache or provide your own.
--
module Typograffiti.Cache where
import Control.Monad.Except (MonadError (..), liftEither)
import Control.Monad.Fail (MonadFail (..))
import Control.Monad.IO.Class (MonadIO (..))
import Data.Bifunctor (first)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B8
import qualified Data.Vector.Unboxed as UV
import Foreign.Marshal.Array (withArray)
import Graphics.GL
import Linear (V2 (..), V3 (..), V4 (..), M44 (..),
(!*!), identity)
import Data.Text.Glyphize (GlyphInfo(..), GlyphPos(..))
import Typograffiti.Atlas
import Typograffiti.GL
-- | Generic operations for text layout.
class Layout t where
translate :: t -> V2 Float -> t
-- | Holds an allocated draw function for some amount of text. The function
-- takes one parameter that can be used to transform the text in various ways.
-- This type is generic and can be used to take advantage of your own font
-- rendering shaders.
data AllocatedRendering t = AllocatedRendering
{ arDraw :: t -> V2 Int -> IO ()
-- ^ Draw the text with some transformation in some monad.
, arRelease :: IO ()
-- ^ Release the allocated draw function in some monad.
, arSize :: V2 Int
-- ^ The size (in pixels) of the drawn text.
}
-- | Constructs a callback for for computing the geometry for
-- rendering given glyphs out of the given texture.
makeDrawGlyphs
:: ( MonadIO m
, MonadError TypograffitiError m
, MonadIO n
, MonadFail n
, MonadError TypograffitiError n
)
=> m (Atlas
-> [(GlyphInfo, GlyphPos)]
-> n (AllocatedRendering [TextTransform])
)
makeDrawGlyphs = do
let position = 0
uv = 1
vert <- liftGL $ compileOGLShader vertexShader GL_VERTEX_SHADER
frag <- liftGL $ compileOGLShader fragmentShader GL_FRAGMENT_SHADER
prog <- liftGL $ compileOGLProgram [
("position", fromIntegral position),
("uv", fromIntegral uv)
] [vert, frag]
glUseProgram prog
glEnable GL_BLEND
glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
-- Get uniform locations
pjU <- getUniformLocation prog "projection"
mvU <- getUniformLocation prog "modelview"
multU <- getUniformLocation prog "mult_color"
texU <- getUniformLocation prog "tex"
return $ \atlas glyphs -> do
vao <- newBoundVAO
pbuf <- newBuffer
uvbuf <- newBuffer
(ps, uvs) <- UV.unzip <$> stringTris' atlas glyphs
bufferGeometry position pbuf ps
bufferGeometry uv uvbuf uvs
glBindVertexArray 0
let draw ts wsz = do
let (mv, multVal) = transformToUniforms ts
glUseProgram prog
let pj = orthoProjection wsz
updateUniform prog pjU pj
updateUniform prog mvU mv
updateUniform prog multU multVal
updateUniform prog texU (0 :: Int)
glBindVertexArray vao
withBoundTextures [atlasTexture atlas] $ do
drawVAO prog vao GL_TRIANGLES (fromIntegral $ UV.length ps)
glBindVertexArray 0
release = do
withArray [pbuf, uvbuf] $ glDeleteBuffers 2
withArray [vao] $ glDeleteVertexArrays 1
(tl, br) = boundingBox ps
size = br - tl
return AllocatedRendering {
arDraw = draw,
arRelease = release,
arSize = round <$> size
}
-- | The GPU code to finalize the position of glyphs onscreen.
vertexShader :: ByteString
vertexShader = B8.pack $ unlines
[ "#version 330 core"
, "uniform mat4 projection;"
, "uniform mat4 modelview;"
, "in vec2 position;"
, "in vec2 uv;"
, "out vec2 fuv;"
, "void main () {"
, " fuv = uv;"
, " gl_Position = projection * modelview * vec4(position.xy, 0.0, 1.0);"
, "}"
]
-- | The GPU code to composite the recoloured glyph into the output image.
fragmentShader :: ByteString
fragmentShader = B8.pack $ unlines
[ "#version 330 core"
, "in vec2 fuv;"
, "out vec4 fcolor;"
, "uniform sampler2D tex;"
, "uniform vec4 mult_color;"
, "void main () {"
, " vec4 tcolor = texture(tex, fuv);"
, " fcolor = vec4(mult_color.rgb, mult_color.a * tcolor.r);"
, "}"
]
------
--- Transforms
------
-- | Geometrically transform the text.
data SpatialTransform = SpatialTransformTranslate (V2 Float)
-- ^ Shift the text horizontally or vertically.
| SpatialTransformScale (V2 Float)
-- ^ Resize the text.
| SpatialTransformRotate Float
-- ^ Enlarge the text.
| SpatialTransformSkew Float
-- ^ Skew the text, approximating italics (or rather obliques).
| SpatialTransform (M44 Float)
-- ^ Apply an arbitrary matrix transform to the text.
-- | Modify the rendered text.
data TextTransform = TextTransformMultiply (V4 Float)
-- ^ Adjust the colour of the rendered text.
| TextTransformSpatial SpatialTransform
-- ^ Adjust the position of the rendered text.
-- | Convert the `TextTransform`s into data that can be sent to the GPU.
transformToUniforms :: [TextTransform] -> (M44 Float, V4 Float)
transformToUniforms = foldl toUniform (identity, 1.0)
where toUniform (mv, clr) (TextTransformMultiply c) =
(mv, clr * c)
toUniform (mv, clr) (TextTransformSpatial s) =
let mv1 = case s of
SpatialTransformTranslate (V2 x y) ->
mv !*! mat4Translate (V3 x y 0)
SpatialTransformScale (V2 x y) ->
mv !*! mat4Scale (V3 x y 1)
SpatialTransformRotate r ->
mv !*! mat4Rotate r (V3 0 0 1)
SpatialTransformSkew x ->
mv !*! mat4SkewXbyY x
SpatialTransform mat -> mv !*! mat
in (mv1, clr)
-- | Shift the text horizontally or vertically.
move :: Float -> Float -> TextTransform
move x y =
TextTransformSpatial
$ SpatialTransformTranslate
$ V2 x y
-- | Resize the text.
scale :: Float -> Float -> TextTransform
scale x y =
TextTransformSpatial
$ SpatialTransformScale
$ V2 x y
-- | Rotate the text.
rotate :: Float -> TextTransform
rotate =
TextTransformSpatial
. SpatialTransformRotate
skew :: Float -> TextTransform
skew = TextTransformSpatial . SpatialTransformSkew
matrix :: M44 Float -> TextTransform
matrix = TextTransformSpatial . SpatialTransform
-- | Recolour the text.
color :: Float -> Float -> Float -> Float -> TextTransform
color r g b a =
TextTransformMultiply
$ V4 r g b a
-- | Make the text semi-transparant.
alpha :: Float -> TextTransform
alpha =
TextTransformMultiply
. V4 1 1 1
instance Layout [TextTransform] where
translate ts (V2 x y) = ts ++ [move x y]
-- | Utility for calling OpenGL APIs in a error monad.
liftGL
:: ( MonadIO m
, MonadError TypograffitiError m
)
=> m (Either String a)
-> m a
liftGL n = do
let lft = liftEither . first TypograffitiErrorGL
n >>= lft