~alcinnz/Typograffiti

f793b68c0e0c21b9c48073ed2d1e83313c38fcef — Adrian Cochrane 1 year, 10 months ago cb478a2
Document everything and get test code working again!
M app/Main.hs => app/Main.hs +27 -21
@@ 2,8 2,11 @@
module Main where

import System.Environment (getArgs)
import Graphics.Text.Font.Render (makeDrawText', GlyphSize(..), TextTransform(..),
                                    AllocatedRendering(..), SpatialTransform(..))
import Typograffiti (makeDrawText', GlyphSize(..), TextTransform(..), txt,
                     SampleText(..), defaultSample, AllocatedRendering(..),
                     SpatialTransform(..))
import Control.Monad.Except (liftEither, runExceptT)
import Control.Monad.IO.Class (MonadIO (..))
import SDL hiding (rotate)
import Graphics.GL.Core32



@@ 26,22 29,25 @@ main = do

    let ttfName = "assets/Lora-Regular.ttf"
    text <- pack <$> unwords <$> getArgs
    drawText <- makeDrawText' ttfName 0 (PixelSize 15 15) [] text
    drawText' <- drawText text

    fix $ \loop -> do
        events <- fmap eventPayload <$> pollEvents
        glClearColor 0 0 0 1
        glClear GL_COLOR_BUFFER_BIT

        sz@(V2 dw dh) <- glGetDrawableSize w
        glViewport 0 0 (fromIntegral dw) (fromIntegral dh)

        let offset = V2 0 $ fromIntegral dy
            V2 _ dy = arSize drawText'
        arDraw drawText' [
            TextTransformSpatial $ SpatialTransformTranslate offset
          ] sz

        glSwapWindow w
        unless (QuitEvent `elem` events) loop
    drawText <- makeDrawText' ttfName 0 (PixelSize 15 15) $ defaultSample { sampleText = text }
    runExceptT $ do
        drawText0 <- liftEither drawText
        drawText' <- drawText0 $ txt text

        fix $ \loop -> do
            events <- fmap eventPayload <$> pollEvents
            liftIO $ glClearColor 0 0 0 1
            liftIO $ glClear GL_COLOR_BUFFER_BIT

            sz@(V2 dw dh) <- liftIO $ glGetDrawableSize w
            liftIO $ glViewport 0 0 (fromIntegral dw) (fromIntegral dh)

            let offset = V2 0 $ fromIntegral dy
                V2 _ dy = arSize drawText'
            liftIO $ arDraw drawText' [
                TextTransformSpatial $ SpatialTransformTranslate $ fromIntegral dy
              ] (fromIntegral <$> sz)

            liftIO $ glSwapWindow w
            unless (QuitEvent `elem` events) loop
    return ()

M src/Typograffiti.hs => src/Typograffiti.hs +2 -2
@@ 17,7 17,7 @@ module Typograffiti(
        varItalic, varOptSize, varSlant, varWidth, varWeight,
    RichText (..), str, txt, ($$), style, apply, on, off, alternate,
        alt, case_, centerCJKPunct, capSpace, ctxtSwash, petiteCaps', smallCaps',
        expertJ, finGlyph, fract, fullWidth, hist, hkana, hlig, hojo, halfWidth,
        expertJ, finGlyph, fract, fullWidth, hist, hkana, histLig, hojo, halfWidth,
        italic, justifyAlt, jap78, jap83, jap90, jap04, kerning, lBounds, liningFig,
        localized, mathGreek, altAnnotation, nlcKanji, oldFig, ordinals, ornament,
        propAltWidth, petiteCaps, propKana, propFig, propWidth, quarterWidth,


@@ 25,7 25,7 @@ module Typograffiti(
        superscript, swash, titling, traditionNameJ, tabularFig, traditionCJ,
        thirdWidth, unicase, vAlt, vert, vHalfAlt, vKanaAlt, vKerning, vPropAlt,
        vRotAlt, vrot, slash0, altFrac, ctxtAlt, ctxtLig, optLigs, lig, rand,
    makeDrawTextCached, makeDrawText
    GlyphSize(..), makeDrawTextCached, makeDrawText, makeDrawText'
) where

import Typograffiti.Atlas

M src/Typograffiti/Atlas.hs => src/Typograffiti/Atlas.hs +36 -0
@@ 40,6 40,7 @@ import           Foreign.C.String                                  (withCString)

import           Typograffiti.GL

-- | Represents a failure to render text.
data TypograffitiError =
    TypograffitiErrorNoGlyphMetricsForChar Char
  -- ^ The are no glyph metrics for this character. This probably means


@@ 54,36 55,57 @@ data TypograffitiError =
--- Atlas
------

-- | Size & position of a Glyph in the `Atlas`.
data GlyphMetrics = GlyphMetrics {
    glyphTexBB :: (V2 Int, V2 Int),
    -- ^ Bounding box of the glyph in the texture.
    glyphTexSize :: V2 Int,
    -- ^ Size of the glyph in the texture.
    glyphSize :: V2 Int
    -- ^ Size of the glyph onscreen.
} deriving (Show, Eq)

-- | Cache of rendered glyphs to be composited into place on the GPU.
data Atlas = Atlas {
    atlasTexture :: GLuint,
    -- ^ The texture holding the pre-rendered glyphs.
    atlasTextureSize :: V2 Int,
    -- ^ The size of the texture.
    atlasMetrics :: IntMap GlyphMetrics,
    -- ^ Mapping from glyphs to their position in the texture.
    atlasFilePath :: FilePath
    -- ^ Filepath for the font.
} deriving (Show)

-- | Initializes an empty atlas.
emptyAtlas :: GLuint -> Atlas
emptyAtlas t = Atlas t 0 mempty ""

-- | Precomputed positioning of glyphs in an `Atlas` texture.
data AtlasMeasure = AM {
    amWH :: V2 Int,
    -- ^ Current size of the atlas as it has been laid out so far.
    amXY :: V2 Int,
    -- ^ Tentative position for the next glyph added to the atlas.
    rowHeight :: Int,
    -- ^ Height of the current row, for the sake of line wrapping.
    amMap :: IntMap (V2 Int)
    -- ^ Position of each glyph in the atlas.
} deriving (Show, Eq)

-- | Initializes a new `AtlasMeasure`.
emptyAM :: AtlasMeasure
emptyAM = AM 0 (V2 1 1) 0 mempty

-- | The amount of spacing between glyphs rendered into the atlas's texture.
spacing :: Int
spacing = 1

-- | Callback for looking up a glyph from an atlas.
-- Useful for applying synthetic styles to fonts which lack them,
-- when calling the low-level APIs.
type GlyphRetriever m = Word32 -> m (FT_Bitmap, FT_Glyph_Metrics)
-- | Default callback for glyph lookups, with no modifications.
glyphRetriever :: MonadIO m => FT_Face -> GlyphRetriever m
glyphRetriever font glyph = liftIO $ do
    ft_Load_Glyph font (fromIntegral $ fromEnum glyph) FT_LOAD_RENDER


@@ 91,6 113,8 @@ glyphRetriever font glyph = liftIO $ do
    slot <- peek $ frGlyph font'
    return (gsrBitmap slot, gsrMetrics slot)

-- | Extract the measurements of a character in the FT_Face and append it to
-- the given AtlasMeasure.
measure :: MonadIO m => GlyphRetriever m -> Int -> AtlasMeasure -> Word32 -> m AtlasMeasure
measure cb maxw am@AM{..} glyph
    | Just _ <- IM.lookup (fromEnum glyph) amMap = return am


@@ 114,6 138,7 @@ measure cb maxw am@AM{..} glyph
              }
        return am

-- | Uploads glyphs into an `Atlas` texture for the GPU to composite.
texturize :: MonadIO m => GlyphRetriever m -> IntMap (V2 Int) -> Atlas -> Word32 -> m Atlas
texturize cb xymap atlas@Atlas{..} glyph
    | Just pos@(V2 x y) <- IM.lookup (fromIntegral $ fromEnum glyph) xymap = do


@@ 136,9 161,14 @@ texturize cb xymap atlas@Atlas{..} glyph
              }
        return atlas { atlasMetrics = IM.insert (fromEnum glyph) mtrcs atlasMetrics }
    | otherwise = do
        -- TODO Throw an exception.
        liftIO $ putStrLn ("Cound not find glyph " ++ show glyph)
        return atlas

-- | 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.
allocAtlas :: (MonadIO m, MonadFail m) => GlyphRetriever m -> [Word32] -> m Atlas
allocAtlas cb glyphs = do
    AM {..} <- foldM (measure cb 512) emptyAM glyphs


@@ 162,10 192,13 @@ allocAtlas cb glyphs = do
    glPixelStorei GL_UNPACK_ALIGNMENT 4
    return atlas { atlasTextureSize = V2 w h }

-- | Releases all resources associated with the given 'Atlas'.
freeAtlas :: MonadIO m => Atlas -> m ()
freeAtlas a = liftIO $ with (atlasTexture a) $ \ptr -> glDeleteTextures 1 ptr

-- | The geometry needed to render some text, with the position for the next glyph.
type Quads = (Float, Float, [Vector (V2 Float, V2 Float)])
-- | Construct the geometry needed to render the given character.
makeCharQuad :: (MonadIO m, MonadError TypograffitiError m) =>
    Atlas -> Quads -> (GlyphInfo, GlyphPos) -> m Quads
makeCharQuad Atlas {..} (penx, peny, mLast) (GlyphInfo {codepoint=glyph}, GlyphPos {..}) = do


@@ 193,11 226,14 @@ makeCharQuad Atlas {..} (penx, peny, mLast) (GlyphInfo {codepoint=glyph}, GlyphP
    f' :: Int -> Float
    f' = fromIntegral

-- | Generate the geometry of the given string, with next-glyph position.
stringTris :: (MonadIO m, MonadError TypograffitiError m) =>
    Atlas -> [(GlyphInfo, GlyphPos)] -> m Quads
stringTris atlas = foldM (makeCharQuad atlas) (0, 0, [])
-- | Generate the geometry of the given string.
stringTris' :: (MonadIO m, MonadError TypograffitiError m) =>
    Atlas -> [(GlyphInfo, GlyphPos)] -> m (Vector (V2 Float, V2 Float))
stringTris' atlas glyphs = do
    (_, _, ret) <- stringTris atlas glyphs
    liftIO $ print ret
    return $ UV.concat $ reverse ret

M src/Typograffiti/Cache.hs => src/Typograffiti/Cache.hs +23 -6
@@ 36,9 36,14 @@ 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.


@@ 48,6 53,8 @@ data AllocatedRendering t = AllocatedRendering
    -- ^ 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


@@ 108,6 115,7 @@ makeDrawGlyphs = do
            arSize = round <$> size
          }

-- | The GPU code to finalize the position of glyphs onscreen.
vertexShader :: ByteString
vertexShader = B8.pack $ unlines
  [ "#version 330 core"


@@ 122,6 130,7 @@ vertexShader = B8.pack $ unlines
  , "}"
  ]

-- | The GPU code to composite the recoloured glyph into the output image.
fragmentShader :: ByteString
fragmentShader = B8.pack $ unlines
  [ "#version 330 core"


@@ 139,15 148,21 @@ fragmentShader = B8.pack $ unlines
--- 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.


-- | 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) =


@@ 162,32 177,33 @@ transformToUniforms = foldl toUniform (identity, 1.0)
                  mv !*! mat4Rotate r (V3 0 0 1)
          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


-- | 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


@@ 197,6 213,7 @@ alpha =
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 src/Typograffiti/GL.hs => src/Typograffiti/GL.hs +22 -17
@@ 25,7 25,7 @@ import           Linear
import           Linear.V               (Finite, Size, dim, toV)
import           Data.List              (foldl')


-- | Allocates a new active texture (image data) in the GPU.
allocAndActivateTex :: (MonadIO m, MonadFail m) => GLenum -> m GLuint
allocAndActivateTex u = do
  [t] <- liftIO $ allocaArray 1 $ \ptr -> do


@@ 35,7 35,7 @@ allocAndActivateTex u = do
  glBindTexture GL_TEXTURE_2D t
  return t


-- | Report any exceptions encounted by OpenGL.
clearErrors :: MonadIO m => String -> m ()
clearErrors str = do
  err' <- glGetError


@@ 43,7 43,7 @@ clearErrors str = do
    liftIO $ putStrLn $ unwords [str, show err']
    assert False $ return ()


-- | Allocates a new, bound Vertex Array Object.
newBoundVAO :: (MonadIO m, MonadFail m) => m GLuint
newBoundVAO = do
  [vao] <- liftIO $ allocaArray 1 $ \ptr -> do


@@ 53,7 53,8 @@ newBoundVAO = do
  return vao



-- | Runs the given callback giving a new temporarily-bound Vertex Array Object,
-- catching any errors.
withVAO :: MonadIO m => (GLuint -> IO b) -> m b
withVAO f = liftIO $ do
  vao <- newBoundVAO


@@ 62,7 63,7 @@ withVAO f = liftIO $ do
  glBindVertexArray 0
  return r


-- | Allocates a new buffer on the GPU.
newBuffer
  :: MonadIO m
  => m GLuint


@@ 72,7 73,7 @@ newBuffer = liftIO $ do
    peekArray 1 ptr
  return b


-- Allocates the given number of buffer objects to pass to the given callback.
withBuffers :: MonadIO m => Int -> ([GLuint] -> m b) -> m b
withBuffers n = (replicateM n newBuffer >>=)



@@ 107,7 108,7 @@ bufferGeometry loc buf as
    glVertexAttribPointer loc n GL_FLOAT GL_FALSE 0 nullPtr
    clearErrors "bufferGeometry"


-- | Converts an unboxed vector to a storable vector suitable for storing in a GPU buffer.
convertVec
  :: (Unbox (f Float), Foldable f) => UV.Vector (f Float) -> SV.Vector GLfloat
convertVec =


@@ 125,6 126,8 @@ withBoundTextures ts f = do
  where bindTex tex u = glActiveTexture u >> glBindTexture GL_TEXTURE_2D tex


-- | Render the given slice of the given Vertex-Array Object with the given program
-- in the given mode, with exception handling.
drawVAO
  :: MonadIO m
  => GLuint


@@ 143,7 146,7 @@ drawVAO program vao mode num = liftIO $ do
  glDrawArrays mode 0 num
  clearErrors "drawBuffer:glDrawArrays"


-- | Compiles GLSL code to GPU opcodes, or returns an error message.
compileOGLShader
  :: MonadIO m
  => ByteString


@@ 183,7 186,8 @@ compileOGLShader src shType = do
          return $ Left err
        else return $ Right shader


-- Combine multiple compiled GLSL shaders into a single program,
-- or returns an error message.
compileOGLProgram
  :: MonadIO m
  => [(String, Integer)]


@@ 227,14 231,15 @@ compileOGLProgram attribs shaders = do
-- Uniform marshaling functions
--------------------------------------------------------------------------------


-- | Lookup ID for a named uniform GLSL variable.
getUniformLocation :: MonadIO m => GLuint -> String -> m GLint
getUniformLocation program ident = liftIO
  $ withCString ident
  $ glGetUniformLocation program


-- | Data that can be uploaded to GLSL uniform variables.
class UniformValue a where
  -- | Upload a value to a GLSL uniform variable.
  updateUniform
    :: MonadIO m
    => GLuint


@@ 245,7 250,7 @@ class UniformValue a where
    -- ^ The value.
    -> m ()


-- | Report exceptions setting GLSL uniform variables.
clearUniformUpdateError :: (MonadIO m, Show a) => GLuint -> GLint -> a -> m ()
clearUniformUpdateError prog loc val = glGetError >>= \case
  0 -> return ()


@@ 328,15 333,15 @@ instance UniformValue (Int,Int) where
-- Matrix helpers
--------------------------------------------------------------------------------


-- | Constructs a matrix that shifts a vector horizontally or vertically.
mat4Translate :: Num a => V3 a -> M44 a
mat4Translate = mkTransformationMat identity


-- | Constructs a matrix that rotates a vector.
mat4Rotate :: (Num a, Epsilon a, Floating a) => a -> V3 a -> M44 a
mat4Rotate phi v = mkTransformation (axisAngle v phi) (V3 0 0 0)


-- | Constructs a matrix that resizes a vector.
mat4Scale :: Num a => V3 a -> M44 a
mat4Scale (V3 x y z) =
    V4 (V4 x 0 0 0)


@@ 344,7 349,7 @@ mat4Scale (V3 x y z) =
       (V4 0 0 z 0)
       (V4 0 0 0 1)


-- | Constructs a matrix that converts screen coordinates to range 1,-1; with perspective.
orthoProjection
  :: Integral a
  => V2 a


@@ 354,7 359,7 @@ orthoProjection (V2 ww wh) =
  let (hw,hh) = (fromIntegral ww, fromIntegral wh)
  in ortho 0 hw hh 0 0 1


-- | Computes the boundingbox for an array of points.
boundingBox :: (Unbox a, Real a, Fractional a) => UV.Vector (V2 a) -> (V2 a, V2 a)
boundingBox vs
  | UV.null vs = (0,0)

M src/Typograffiti/Rich.hs => src/Typograffiti/Rich.hs +286 -11
@@ 5,8 5,11 @@ import           Data.Text.Glyphize (Feature(..), tag_from_string, parseFeature)
import           Data.String        (IsString(..))
import           Data.Word          (Word32)

-- | Retreives the length of some text as a `Word` suitable for storing in a `Feature`.
length' :: Text -> Word
length' = toEnum . fromEnum . Txt.length

-- | Styled text to be rendered.
data RichText = RichText {
    text :: Text,
    features :: [Feature]


@@ 14,11 17,14 @@ data RichText = RichText {

instance IsString RichText where
    fromString x = flip RichText [] $ pack x
-- | Converts a `String` to renderable `RichText`.
str :: String -> RichText
str = fromString
-- | Converts `Text` to renderable `RichText`.
txt :: Text -> RichText
txt = flip RichText []

-- | Concatenate richtext data.
($$) :: RichText -> RichText -> RichText
RichText ltext lfeat $$ RichText rtext rfeat = RichText {
    text = append ltext rtext,


@@ 27,11 33,17 @@ RichText ltext lfeat $$ RichText rtext rfeat = RichText {
        | feat@Feature { featStart = start, featEnd = end } <- rfeat]
  }

-- | Applies the given OpenType Feature to the given `RichText`.
-- Check your font for details on which OpenType features are supported.
-- Or see https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist/
-- (from which much of this documentation is taken).
style :: String -> Word32 -> RichText -> RichText
style feat value (RichText text feats) = RichText {
    text = text,
    features = Feature (tag_from_string feat) value 0 (length' text) : feats
  }
-- | Parses the given syntax akin to CSS font-feature-settings & apply to
-- The given RichText.
apply :: String -> RichText -> RichText
apply syntax rich | Just feat <- parseFeature syntax = rich {
      features = feat { featStart = 0, featEnd = length' $ text rich } : features rich


@@ 39,90 51,353 @@ apply syntax rich | Just feat <- parseFeature syntax = rich {
  | otherwise = rich

alt, case_, centerCJKPunct, capSpace, ctxtSwash, petiteCaps', smallCaps', expertJ,
    finGlyph, fract, fullWidth, hist, hkana, hlig, hojo, halfWidth, italic, justifyAlt,
    jap78, jap83, jap90, jap04, kerning, lBounds, liningFig, localized, mathGreek,
    altAnnotation, nlcKanji, oldFig, ordinals, ornament, propAltWidth, petiteCaps,
    propKana, propFig, propWidth, quarterWidth, rBounds, ruby, styleAlt, sciInferior,
    smallCaps, simpleCJ, subscript, superscript, swash, titling, traditionNameJ,
    tabularFig, traditionCJ, thirdWidth, unicase, vAlt, vert, vHalfAlt, vKanaAlt,
    vKerning, vPropAlt, vRotAlt, vrot, slash0 :: Word32 -> RichText -> RichText
altFrac, ctxtAlt, ctxtLig, optLigs, lig, rand :: Bool -> RichText -> RichText
    finGlyph, fract, fullWidth, hist, hkana, histLig, hojo, halfWidth, italic,
    justifyAlt, jap78, jap83, jap90, jap04, kerning, lBounds, liningFig, localized,
    mathGreek, altAnnotation, nlcKanji, oldFig, ordinals, ornament, propAltWidth,
    petiteCaps, propKana, propFig, propWidth, quarterWidth, rand, rBounds, ruby,
    styleAlt, sciInferior, smallCaps, simpleCJ, subscript, superscript, swash,
    titling, traditionNameJ, tabularFig, traditionCJ, thirdWidth, unicase, vAlt,
    vert, vHalfAlt, vKanaAlt, vKerning, vPropAlt, vRotAlt, vrot,
    slash0 :: Word32 -> RichText -> RichText
altFrac, ctxtAlt, ctxtLig, optLigs, lig :: Bool -> RichText -> RichText
-- | This feature makes all variations of a selected character accessible.
-- This serves several purposes: An application may not support the feature by
-- which the desired glyph would normally be accessed; the user may need a glyph
-- outside the context supported by the normal substitution, or the user may not
-- know what feature produces the desired glyph. Since many-to-one substitutions
-- are not covered, ligatures would not appear in this table unless they were
-- variant forms of another ligature.
alt         = style "aalt"
-- | Replaces figures separated by a slash with an alternative form.
altFrac True= style "afrc" 4
altFrac False=style "afrc" 0
-- | n specified situations, replaces default glyphs with alternate forms which
-- provide better joining behavior. Used in script typefaces which are designed
-- to have some or all of their glyphs join.
ctxtAlt True= style "calt" 6
ctxtAlt False=style "calt" 0
-- | Shifts various punctuation marks up to a position that works better with
-- all-capital sequences or sets of lining figures; also changes oldstyle
-- figures to lining figures. By default, glyphs in a text face are designed to
-- work with lowercase characters. Some characters should be shifted vertically
-- to fit the higher visual center of all-capital or lining text. Also, lining
-- figures are the same height (or close to it) as capitals, and fit much better
-- with all-capital text.
case_       = style "case"
-- | Replaces a sequence of glyphs with a single glyph which is preferred for
-- typographic purposes. Unlike other ligature features, 'clig' specifies the
-- context in which the ligature is recommended. This capability is important
-- in some script designs and for swash ligatures.
ctxtLig True= style "clig" 8
ctxtLig False=style "clig" 0
-- | Centers specific punctuation marks for those fonts that do not include
-- centered and non-centered forms.
centerCJKPunct = style "cpct"
-- | Globally adjusts inter-glyph spacing for all-capital text. Most typefaces
-- contain capitals and lowercase characters, and the capitals are positioned to
-- work with the lowercase. When capitals are used for words, they need more
-- space between them for legibility and esthetics. This feature would not apply
-- to monospaced designs. Of course the user may want to override this behavior
-- in order to do more pronounced letterspacing for esthetic reasons.
capSpace    = style "cpsp"
-- | This feature replaces default character glyphs with corresponding swash
-- glyphs in a specified context. Note that there may be more than one swash
-- alternate for a given character.
ctxtSwash   = style "cswh"
-- | This feature turns capital characters into petite capitals. It is generally
-- used for words which would otherwise be set in all caps, such as acronyms,
-- but which are desired in petite-cap form to avoid disrupting the flow of text.
-- See the 'pcap' feature description for notes on the relationship of caps,
-- smallcaps and petite caps.
petiteCaps' = style "c2pc"
-- | This feature turns capital characters into small capitals. It is generally
-- used for words which would otherwise be set in all caps, such as acronyms,
-- but which are desired in small-cap form to avoid disrupting the flow of text.
smallCaps'  = style "c2sc"
-- | Replaces a sequence of glyphs with a single glyph which is preferred for
-- typographic purposes. This feature covers those ligatures which may be used
-- for special effect, at the user’s preference.
optLigs True= style "dlig" 4
optLigs False=style "dlig" 0
-- | Like the JIS78 Forms feature, this feature replaces standard forms in
-- Japanese fonts with corresponding forms preferred by typographers. Although
-- most of the JIS78 substitutions are included, the expert substitution goes on
-- to handle many more characters.
expertJ     = style "expt"
-- | Replaces line final glyphs with alternate forms specifically designed for
-- this purpose (they would have less or more advance width as need may be), to
-- help justification of text.
finGlyph    = style "falt"
-- | Replaces figures (digits) separated by a slash (U+002F or U+2044) with
-- “common” (diagonal) fractions.
fract       = style "frac"
-- | Replaces glyphs set on other widths with glyphs set on full (usually em)
-- widths. In a CJKV font, this may include “lower ASCII” Latin characters and
-- various symbols. In a European font, this feature replaces proportionally-spaced
-- glyphs with monospaced glyphs, which are generally set on widths of 0.6 em.
fullWidth   = style "fwid"
-- | Some letterforms were in common use in the past, but appear anachronistic
-- today. The best-known example is the long form of s; others would include the
-- old Fraktur k. Some fonts include the historical forms as alternates, so they
-- can be used for a “period” effect. This feature replaces the default (current)
-- forms with the historical alternates. While some ligatures are also used for
-- historical effect, this feature deals only with single characters.
hist        = style "hist"
-- | Replaces standard kana with forms that have been specially designed for only
-- horizontal writing. This is a typographic optimization for improved fit and
-- more even color. Also see 'vkana'.
hkana       = style "hkna"
hlig        = style "hlig"
-- | Some ligatures were in common use in the past, but appear anachronistic today.
-- Some fonts include the historical forms as alternates, so they can be used for
-- a “period” effect. This feature replaces the default (current) forms with the
-- historical alternates.
histLig        = style "hlig"
-- | The JIS X 0212-1990 (aka, “Hojo Kanji”) and JIS X 0213:2004 character sets
-- overlap significantly. In some cases their prototypical glyphs differ. When
-- building fonts that support both JIS X 0212-1990 and JIS X 0213:2004 (such as
-- those supporting the Adobe-Japan 1-6 character collection), it is recommended
-- that JIS X 0213:2004 forms be preferred as the encoded form. The 'hojo'
-- feature is used to access the JIS X 0212-1990 glyphs for the cases when the
-- JIS X 0213:2004 form is encoded.
hojo        = style "hojo"
-- | Replaces glyphs on proportional widths, or fixed widths other than half an
-- em, with glyphs on half-em (en) widths. Many CJKV fonts have glyphs which are
-- set on multiple widths; this feature selects the half-em version. There are
-- various contexts in which this is the preferred behavior, including
-- compatibility with older desktop documents.
halfWidth   = style "hwid"
-- | Some fonts (such as Adobe’s Pro Japanese fonts) will have both Roman and
-- Italic forms of some characters in a single font. This feature replaces the
-- Roman glyphs with the corresponding Italic glyphs.
italic      = style "ital"
-- | Improves justification of text by replacing glyphs with alternate forms
-- specifically designed for this purpose (they would have less or more advance
-- width as need may be).
justifyAlt  = style "jalt"
-- | This feature replaces default (JIS90) Japanese glyphs with the corresponding
-- forms from the JIS C 6226-1978 (JIS78) specification.
jap78       = style "jp78"
-- | This feature replaces default (JIS90) Japanese glyphs with the corresponding
-- forms from the JIS X 0208-1983 (JIS83) specification.
jap83       = style "jp83"
-- | This feature replaces Japanese glyphs from the JIS78 or JIS83 specifications
-- with the corresponding forms from the JIS X 0208-1990 (JIS90) specification.
jap90       = style "jp90"
-- | The National Language Council (NLC) of Japan has defined new glyph shapes
-- for a number of JIS characters, which were incorporated into JIS X 0213:2004
-- as new prototypical forms. The 'jp04' feature is a subset of the 'nlck'
-- feature, and is used to access these prototypical glyphs in a manner that
-- maintains the integrity of JIS X 0213:2004.
jap04       = style "jp04"
-- | Adjusts amount of space between glyphs, generally to provide optically
-- consistent spacing between glyphs. Although a well-designed typeface has
-- consistent inter-glyph spacing overall, some glyph combinations require
-- adjustment for improved legibility. Besides standard adjustment in the
-- horizontal direction, this feature can supply size-dependent kerning data
-- via device tables, “cross-stream” kerning in the Y text direction, and
-- adjustment of glyph placement independent of the advance adjustment. Note
-- that this feature may apply to runs of more than two glyphs, and would not
-- be used in monospaced fonts. Also note that this feature does not apply to
-- text set vertically.
kerning     = style "kern"
-- | Aligns glyphs by their apparent left extents at the left ends of horizontal
-- lines of text, replacing the default behavior of aligning glyphs by their origins.
lBounds     = style "lfbd"
-- | Replaces a sequence of glyphs with a single glyph which is preferred for
-- typographic purposes. This feature covers the ligatures which the
-- designer or manufacturer judges should be used in normal conditions.
lig True    = style "liga" 4
lig False   = style "liga" 0
-- | This feature changes selected non-lining figures (digits) to lining figures.
liningFig   = style "lnum"
-- | Many scripts used to write multiple languages over wide geographical areas
-- have developed localized variant forms of specific letters, which are used by
-- individual literary communities. For example, a number of letters in the
-- Bulgarian and Serbian alphabets have forms distinct from their Russian
-- counterparts and from each other. In some cases the localized form differs
-- only subtly from the script “norm”, in others the forms are radically distinct.
-- This feature enables localized forms of glyphs to be substituted for default forms.
localized   = style "locl"
-- | Replaces standard typographic forms of Greek glyphs with corresponding forms
-- commonly used in mathematical notation (which are a subset of the Greek alphabet).
mathGreek   = style "mgrk"
-- | Replaces default glyphs with various notational forms (e.g. glyphs placed
-- in open or solid circles, squares, parentheses, diamonds or rounded boxes).
-- In some cases an annotation form may already be present, but the user may want
-- a different one.
altAnnotation=style "nalt"
-- | The National Language Council (NLC) of Japan has defined new glyph shapes
-- for a number of JIS characters in 2000.
nlcKanji    = style "nlck"
-- | This feature changes selected figures from the default or lining style to
-- oldstyle form.
oldFig      = style "onum"
-- | Replaces default alphabetic glyphs with the corresponding ordinal forms for
-- use after figures. One exception to the follows-a-figure rule is the numero
-- character (U+2116), which is actually a ligature substitution, but is best
-- accessed through this feature.
ordinals    = style "ordn"
-- | This is a dual-function feature, which uses two input methods to give the
-- user access to ornament glyphs (e.g. fleurons, dingbats and border elements)
-- in the font. One method replaces the bullet character with a selection from
-- the full set of available ornaments; the other replaces specific “lower ASCII”
-- characters with ornaments assigned to them. The first approach supports the
-- general or browsing user; the second supports the power user.
ornament    = style "ornm"
-- | Re-spaces glyphs designed to be set on full-em widths, fitting them onto
-- individual (more or less proportional) horizontal widths. This differs from
-- 'pwid' in that it does not substitute new glyphs (GPOS, not GSUB feature).
-- The user may prefer the monospaced form, or may simply want to ensure that
-- the glyph is well-fit and not rotated in vertical setting (Latin forms
-- designed for proportional spacing would be rotated).
propAltWidth= style "palt"
-- | Some fonts contain an additional size of capital letters, shorter than the
-- regular smallcaps and whimsically referred to as petite caps. Such forms are
-- most likely to be found in designs with a small lowercase x-height, where they
-- better harmonise with lowercase text than the taller smallcaps (for examples
-- of petite caps, see the Emigre type families Mrs Eaves and Filosofia). This
-- feature turns glyphs for lowercase characters into petite capitals. Forms
-- related to petite capitals, such as specially designed figures, may be included.
petiteCaps  = style "pcap"
-- | Replaces glyphs, kana and kana-related, set on uniform widths (half or
-- full-width) with proportional glyphs.
propKana    = style "pkna"
-- | Replaces figure glyphs set on uniform (tabular) widths with corresponding
-- glyphs set on glyph-specific (proportional) widths. Tabular widths will
-- generally be the default, but this cannot be safely assumed. Of course this
-- feature would not be present in monospaced designs.
propFig     = style "pnum"
-- | Replaces glyphs set on uniform widths (typically full or half-em) with
-- proportionally spaced glyphs. The proportional variants are often used for the
-- Latin characters in CJKV fonts, but may also be used for Kana in Japanese fonts.
propWidth   = style "pwid"
-- | Replaces glyphs on other widths with glyphs set on widths of one quarter
-- of an em (half an en). The characters involved are normally figures and
-- some forms of punctuation.
quarterWidth= style "qwid"
rand True   = style "rand" 3
rand False  = style "rand" 0
-- | In order to emulate the irregularity and variety of handwritten text, this
-- feature allows multiple alternate forms to be used.
rand        = style "rand"
-- | Aligns glyphs by their apparent right extents at the right ends of horizontal
-- lines of text, replacing the default behavior of aligning glyphs by their origins.
rBounds     = style "rtbd"
-- | Japanese typesetting often uses smaller kana glyphs, generally in
-- superscripted form, to clarify the meaning of kanji which may be unfamiliar
-- to the reader. These are called “ruby”, from the old typesetting term for
-- four-point-sized type. This feature identifies glyphs in the font which have
-- been designed for this use, substituting them for the default designs.
ruby        = style "ruby"
-- | Many fonts contain alternate glyph designs for a purely esthetic effect;
-- these don’t always fit into a clear category like swash or historical. As in
-- the case of swash glyphs, there may be more than one alternate form. This
-- feature replaces the default forms with the stylistic alternates.
styleAlt    = style "salt"
-- | Replaces lining or oldstyle figures (digits) with inferior figures (smaller
-- glyphs which sit lower than the standard baseline, primarily for chemical or
-- mathematical notation). May also replace glyphs for lowercase characters with
-- alphabetic inferiors.
sciInferior = style "sinf"
-- | This feature turns glyphs for lowercase characters into small capitals. It
-- is generally used for display lines set in Large & small caps, such as titles.
-- Forms related to small capitals, such as oldstyle figures, may be included.
smallCaps   = style "smcp"
-- | Replaces “traditional” Chinese or Japanese forms with the corresponding
-- “simplified” forms.
simpleCJ    = style "smpl"
-- | The 'subs' feature may replace a default glyph with a subscript glyph, or it
-- may combine a glyph substitution with positioning adjustments for proper placement.
subscript   = style "subs"
-- | Replaces lining or oldstyle figures with superior figures (primarily for
-- footnote indication), and replaces lowercase letters with superior letters
-- (primarily for abbreviated French titles).
superscript = style "sups"
-- | This feature replaces default character glyphs with corresponding swash glyphs.
-- Note that there may be more than one swash alternate for a given character.
swash       = style "swsh"
-- | This feature replaces the default glyphs with corresponding forms designed
-- specifically for titling. These may be all-capital and\/or larger on the body,
-- and adjusted for viewing at larger sizes.
titling     = style "titl"
-- | Replaces “simplified” Japanese kanji forms with the corresponding
-- “traditional” forms. This is equivalent to the Traditional Forms feature,
-- but explicitly limited to the traditional forms considered proper for use
-- in personal names (as many as 205 glyphs in some fonts).
traditionNameJ = style "tnam"
-- | Replaces figure glyphs set on proportional widths with corresponding glyphs
-- set on uniform (tabular) widths. Tabular widths will generally be the default,
-- but this cannot be safely assumed. Of course this feature would not be present
-- in monospaced designs.
tabularFig  = style "tnum"
-- | Replaces 'simplified' Chinese hanzi or Japanese kanji forms with the
-- corresponding 'traditional' forms.
traditionCJ = style "trad"
-- | Replaces glyphs on other widths with glyphs set on widths of one third of an
-- em. The characters involved are normally figures and some forms of punctuation.
thirdWidth  = style "twid"
-- | This feature maps upper- and lowercase letters to a mixed set of lowercase
-- and small capital forms, resulting in a single case alphabet (for an example
-- of unicase, see the Emigre type family Filosofia). The letters substituted
-- may vary from font to font, as appropriate to the design. If aligning to the
-- x-height, smallcap glyphs may be substituted, or specially designed unicase
-- forms might be used. Substitutions might also include specially designed figures.
unicase     = style "unic"
-- | Repositions glyphs to visually center them within full-height metrics, for
-- use in vertical setting. Applies to full-width Latin, Greek, or Cyrillic
-- glyphs, which are typically included in East Asian fonts, and whose glyphs
-- are aligned on a common horizontal baseline and not rotated relative to the
-- page or text frame.
vAlt        = style "valt"
-- | Transforms default glyphs into glyphs that are appropriate for upright
-- presentation in vertical writing mode. While the glyphs for most characters
-- in East Asian writing systems remain upright when set in vertical writing
-- mode, some must be transformed — usually by rotation, shifting, or different
-- component ordering — for vertical writing mode.
vert        = style "vert"
-- | Re-spaces glyphs designed to be set on full-em heights, fitting them onto
-- half-em heights. This differs from 'valt', which repositions a glyph but does
-- not affect its advance.
vHalfAlt    = style "vhal"
-- | Replaces standard kana with forms that have been specially designed for only
-- vertical writing. This is a typographic optimization for improved fit and more
-- even color. Also see 'hkna'.
vKanaAlt    = style "vkna"
-- | Adjusts amount of space between glyphs, generally to provide optically
-- consistent spacing between glyphs. Although a well-designed typeface has
-- consistent inter-glyph spacing overall, some glyph combinations require
-- adjustment for improved legibility. Besides standard adjustment in the
-- vertical direction, this feature can supply size-dependent kerning data
-- via device tables, “cross-stream” kerning in the X text direction, and
-- adjustment of glyph placement independent of the advance adjustment. Note
-- that this feature may apply to runs of more than two glyphs, and would not
-- be used in monospaced fonts. Also note that this feature applies only to
-- text set vertically.
vKerning    = style "vkrn"
-- | Re-spaces glyphs designed to be set on full-em heights, fitting them onto
-- individual (more or less proportional) vertical heights. This differs from
-- 'valt', which repositions a glyph but does not affect its advance.
vPropAlt    = style "vpal"
-- | Replaces some fixed-width (half-, third- or quarter-width) or
-- proportional-width glyphs (mostly Latin or katakana) with forms suitable for
-- vertical writing (that is, rotated 90 degrees clockwise). Note that these are
-- a superset of the glyphs covered in the 'vert' table.
vRotAlt     = style "vrt2"
-- | Transforms default glyphs into glyphs that are appropriate for sideways
-- presentation in vertical writing mode. While the glyphs for most characters
-- in East Asian writing systems remain upright when set in vertical writing mode,
-- glyphs for other characters — such as those of other scripts or for particular
-- Western-style punctuation — are expected to be presented sideways in vertical writing.
vrot        = style "vrtr"
-- | Some fonts contain both a default form of zero, and an alternative form
-- which uses a diagonal slash through the counter. Especially in condensed
-- designs, it can be difficult to distinguish between 0 and O (zero and capital
-- O) in any situation where capitals and lining figures may be arbitrarily mixed.
-- This feature allows the user to change from the default 0 to a slashed form.
slash0      = style "zero"

off, on, alternate :: Word32
-- | Typical word to turn a font-feature off.
off = 0
-- | Typical word to turn a font-feature on
on = 1
-- | Typical word to switch to the alternate setting for a font-feature.
alternate = 3

M src/Typograffiti/Store.hs => src/Typograffiti/Store.hs +21 -1
@@ 20,6 20,7 @@ import           Control.Concurrent.STM (TMVar, atomically, newTMVar, putTMVar,
import           Control.Monad.Except   (MonadError (..), liftEither, runExceptT, ExceptT (..))
import           Control.Monad.IO.Class (MonadIO (..))
import           Control.Monad.Fail     (MonadFail (..))
import           Control.Monad          (unless)
import           Data.Map               (Map)
import qualified Data.Map               as M
import           Data.Set               (Set)


@@ 41,17 42,27 @@ import           Typograffiti.Cache
import           Typograffiti.Text      (GlyphSize(..), drawLinesWrapper, SampleText(..))
import           Typograffiti.Rich      (RichText(..))

-- | Stored fonts at specific sizes.
data FontStore n = FontStore {
    fontMap :: TMVar (Map (FilePath, GlyphSize, Int) Font),
    -- ^ Map for looking up previously-opened fonts & their atlases.
    drawGlyphs :: Atlas -> [(GlyphInfo, GlyphPos)] -> n (AllocatedRendering [TextTransform]),
    -- ^ Cached routine for compositing from the given atlas.
    lib :: FT_Library
    -- ^ Globals for FreeType.
  }
-- | An opened font. In Harfbuzz, FreeType, & Atlas formats.
data Font = Font {
    harfbuzz :: HB.Font,
    -- ^ Font as represented by Harfbuzz.
    freetype :: FT_Face,
    -- ^ Font as represented by FreeType.
    atlases :: TMVar [(IS.IntSet, Atlas)]
    -- ^ Glyphs from the font rendered into GPU atleses.
  }

-- | Opens a font sized to given value & prepare to render text in it.
-- The fonts are cached for later reuse.
makeDrawTextCached :: (MonadIO m, MonadFail m, MonadError TypograffitiError m,
    MonadIO n, MonadFail n, MonadError TypograffitiError n) =>
    FontStore n -> FilePath -> Int -> GlyphSize -> SampleText ->


@@ 76,6 87,8 @@ makeDrawTextCached store filepath index fontsize SampleText {..} = do
    return $ drawLinesWrapper tabwidth $ \RichText {..} -> drawGlyphs store atlas $
        shape (harfbuzz font) defaultBuffer { HB.text = text } []

-- | Opens & sizes the given font using both FreeType & Harfbuzz,
-- loading it into the `FontStore` before returning.
allocFont :: (MonadIO m) =>
        FontStore n -> FilePath -> Int -> GlyphSize -> HB.FontOptions -> m Font
allocFont FontStore {..} filepath index fontsize options = liftIO $ do


@@ 88,7 101,9 @@ allocFont FontStore {..} filepath index fontsize options = liftIO $ do

    bytes <- B.readFile filepath
    let font' = HB.createFontWithOptions options $ HB.createFace bytes $ toEnum index
    liftIO $ ft_Set_Var_Design_Coordinates font $ map float2fixed $ HB.fontVarCoordsDesign font'

    let designCoords = map float2fixed $ HB.fontVarCoordsDesign font'
    unless (null designCoords) $ liftIO $ ft_Set_Var_Design_Coordinates font designCoords

    atlases <- liftIO $ atomically $ newTMVar []
    let ret = Font font' font atlases


@@ 102,6 117,8 @@ allocFont FontStore {..} filepath index fontsize options = liftIO $ do
    float2fixed :: Float -> FT_Fixed
    float2fixed = toEnum . fromEnum . (*65536)

-- | Allocates a new Atlas for the given font & glyphset,
-- loading it into the atlas cache before returning.
allocAtlas' :: (MonadIO m, MonadFail m) =>
    TMVar [(IS.IntSet, Atlas)] -> FT_Face -> IS.IntSet -> m Atlas
allocAtlas' atlases font glyphset = do


@@ 113,11 130,14 @@ allocAtlas' atlases font glyphset = do
        putTMVar atlases $ ((glyphset, atlas):a)
    return atlas

-- | Runs the given callback with a new `FontStore`.
-- Due to FreeType limitations this font store should not persist outside the callback.
withFontStore :: (MonadIO n, MonadError TypograffitiError n, MonadFail n) =>
    (FontStore n -> ExceptT TypograffitiError IO a) ->
    IO (Either TypograffitiError a)
withFontStore cb = ft_With_FreeType $ \lib -> runExceptT $ (newFontStore lib >>= cb)

-- | Allocates a new FontStore wrapping given FreeType state.
newFontStore :: (MonadIO m, MonadError TypograffitiError m,
    MonadIO n, MonadError TypograffitiError n, MonadFail n) => FT_Library -> m (FontStore n)
newFontStore lib = do

M src/Typograffiti/Text.hs => src/Typograffiti/Text.hs +39 -4
@@ 21,7 21,7 @@ import           Control.Concurrent.STM (TMVar, atomically, newTMVar, putTMVar,
import           Control.Monad.Except   (MonadError (..), liftEither, runExceptT)
import           Control.Monad.Fail     (MonadFail (..))
import           Control.Monad.IO.Class (MonadIO (..))
import           Control.Monad          (foldM, forM)
import           Control.Monad          (foldM, forM, unless)
import           Data.Map               (Map)
import qualified Data.Map               as M
import           Data.Set               (Set)


@@ 44,19 44,34 @@ import           Typograffiti.Atlas
import           Typograffiti.Cache
import           Typograffiti.Rich      (RichText(..))

-- | How large the text should be rendered.
data GlyphSize = CharSize Float Float Int Int
                -- ^ Size in Pts at given DPI.
               | PixelSize Int Int
               -- ^ Size in device pixels.
               deriving (Show, Eq, Ord)

-- | Extra parameters for constructing a font atlas,
-- and determining which glyphs should be in it.
data SampleText = SampleText {
    sampleFeatures :: [HB.Feature],
    -- ^ Which OpenType Features you want available to be used in the rendered text.
    -- Defaults to none.
    sampleText :: Text,
    -- ^ Indicates which characters & ligatures will be in the text to be rendered.
    -- Defaults to ASCII, no ligatures.
    tabwidth :: Int,
    -- ^ How many spaces wide should a tab be rendered?
    -- Defaults to 4.
    fontOptions :: FontOptions
    -- ^ Additional font options offered by Harfbuzz.
}

-- | Constructs a `SampleText` with default values.
defaultSample :: SampleText
defaultSample = SampleText [] (pack $ map toEnum [32..126]) 4 defaultFontOptions
-- | Appends an OpenType feature callers may use to the `Sample` ensuring its
-- glyphs are available. Call after setting `sampleText`.
addSampleFeature :: String -> Word32 -> SampleText -> SampleText
addSampleFeature name value sample@SampleText {..} = sample {
        sampleFeatures =


@@ 67,13 82,18 @@ addSampleFeature name value sample@SampleText {..} = sample {
    i = w $ length sampleFeatures
    w :: Int -> Word
    w = toEnum
-- | Parse an OpenType feature into this font using syntax akin to
-- CSS font-feature-settings.
parseSampleFeature :: String -> SampleText -> SampleText
parseSampleFeature syntax sample | Just feat <- parseFeature syntax = sample {
        sampleFeatures = feat : sampleFeatures sample
    }
  | otherwise = sample
-- | Parse multiple OpenType features into this font.
parseSampleFeatures :: [String] -> SampleText -> SampleText
parseSampleFeatures = flip $ foldl $ flip parseSampleFeature
-- | Alter which OpenType variant of this font will be rendered.
-- Please check your font which variants are supported.
addFontVariant :: String -> Float -> SampleText -> SampleText
addFontVariant name val sampleText = sampleText {
    fontOptions = (fontOptions sampleText) {


@@ 81,6 101,8 @@ addFontVariant name val sampleText = sampleText {
            optionVariations (fontOptions sampleText)
    }
  }
-- | Parse a OpenType variant into the configured font using syntax akin to
-- CSS font-variant-settings.
parseFontVariant :: String -> SampleText -> SampleText
parseFontVariant syntax sample | Just var <- parseVariation syntax = sample {
        fontOptions = (fontOptions sample) {


@@ 88,15 110,23 @@ parseFontVariant syntax sample | Just var <- parseVariation syntax = sample {
        }
    }
  | otherwise = sample
-- | Parse multiple OpenType variants into this font.
parseFontVariants :: [String] -> SampleText -> SampleText
parseFontVariants = flip $ foldl $ flip parseFontVariant

-- | Standard italic font variant. Please check if your font supports this.
varItalic = "ital"
-- | Standard optical size font variant. Please check if your font supports this.
varOptSize = "opsz"
-- | Standard slant (oblique) font variant. Please check if your font supports this.
varSlant = "slnt"
-- | Standard width font variant. Please check if your font supports this.
varWidth = "wdth"
-- | Standard weight (boldness) font variant. Please check if your font supports this.
varWeight = "wght"

-- | Opens a font sized to the given value & prepare to render text in it.
-- There is no need to keep the given `FT_Library` live before rendering the text.
makeDrawText :: (MonadIO m, MonadFail m, MonadError TypograffitiError m,
    MonadIO n, MonadFail n, MonadError TypograffitiError n) =>
    FT_Library -> FilePath -> Int -> GlyphSize -> SampleText ->


@@ 116,22 146,27 @@ makeDrawText lib filepath index fontsize SampleText {..} = do
                HB.text = Txt.replicate (toEnum $ succ $ length sampleFeatures) sampleText
            } sampleFeatures
    let glyphs' = map toEnum $ IS.toList $ IS.fromList $ map fromEnum glyphs
    -- FIXME expose this function...
    liftIO $ ft_Set_Var_Design_Coordinates font $ map float2fixed $ HB.fontVarCoordsDesign font'

    let designCoords = map float2fixed $ HB.fontVarCoordsDesign font'
    unless (null designCoords) $ liftIO $ ft_Set_Var_Design_Coordinates font designCoords

    atlas <- allocAtlas (glyphRetriever font) glyphs'
    liftIO $ ft_Done_Face font

    drawGlyphs <- makeDrawGlyphs
    return $ drawLinesWrapper tabwidth $ \RichText {..} ->
    -- FIXME get drawLinesWrapper working again!
    return $ \RichText {..} ->
        drawGlyphs atlas $ shape font' defaultBuffer { HB.text = text } features
  where
    x2 = (*2)
    float2fixed :: Float -> FT_Fixed
    float2fixed = toEnum . fromEnum . (*65536)

-- | Variant of `makeDrawText` which initializes FreeType itself.
makeDrawText' a b c d =
    ft_With_FreeType $ \ft -> runExceptT $ makeDrawText ft a b c d

-- | Internal utility for rendering multiple lines of text & expanding tabs as configured.
drawLinesWrapper :: (MonadIO m, MonadFail m) =>
    Int -> (RichText -> m (AllocatedRendering [TextTransform])) ->
    RichText -> m (AllocatedRendering [TextTransform])

M typograffiti.cabal => typograffiti.cabal +5 -5
@@ 40,9 40,9 @@ library
  default-language:    Haskell2010


--executable typograffiti
--  main-is:             Main.hs
--  build-depends:       base >=4.12 && <4.13, typograffiti, sdl2 >= 2.5.4, text, gl
--  hs-source-dirs:      app
--  default-language:    Haskell2010
executable typograffiti
  main-is:             Main.hs
  build-depends:       base >=4.12 && <4.13, typograffiti, sdl2 >= 2.5.4, text, gl, mtl
  hs-source-dirs:      app
  default-language:    Haskell2010