packages feed

monomer-1.0.0.0: src/Monomer/Graphics/FontManager.hs

{-|
Module      : Monomer.Graphics.FontManager
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Provides functions for getting text dimensions and metrics.
-}
{-# LANGUAGE RecordWildCards #-}

module Monomer.Graphics.FontManager (
  makeFontManager
) where

import Control.Monad (foldM, when)

import Data.Default
import Data.Sequence (Seq)
import Data.Text (Text)
import System.IO.Unsafe

import qualified Data.Sequence as Seq
import qualified Data.Text as T

import Monomer.Common.BasicTypes
import Monomer.Graphics.FFI
import Monomer.Graphics.Types

-- | Creates a font manager instance.
makeFontManager
  :: [FontDef]    -- ^ The font definitions.
  -> Double       -- ^ The device pixel rate.
  -> IO FontManager  -- ^ The created renderer.
makeFontManager fonts dpr = do
  ctx <- fmInit 1 --dpr

  validFonts <- foldM (loadFont ctx) [] fonts

  when (null validFonts) $
    putStrLn "Could not find any valid fonts. Text size calculations will fail."
  
  return $ newManager ctx dpr

newManager :: FMContext -> Double -> FontManager
newManager ctx dpr = FontManager {..} where
  computeTextMetrics font fontSize = unsafePerformIO $ do
    setFont ctx dpr font fontSize def
    (asc, desc, lineh) <- fmTextMetrics ctx
    lowerX <- Seq.lookup 0 <$> fmTextGlyphPositions ctx 0 0 "x"
    let heightLowerX = case lowerX of
          Just lx -> glyphPosMaxY lx - glyphPosMinY lx
          Nothing -> realToFrac asc

    return $ TextMetrics {
      _txmAsc = asc / dpr,
      _txmDesc = desc / dpr,
      _txmLineH = lineh / dpr,
      _txmLowerX = realToFrac heightLowerX / dpr
    }

  computeTextSize font fontSize fontSpaceH text = unsafePerformIO $ do
    setFont ctx dpr font fontSize fontSpaceH
    (x1, y1, x2, y2) <- if text /= ""
      then fmTextBounds ctx 0 0 text
      else do
        (asc, desc, lineh) <- fmTextMetrics ctx
        return (0, 0, 0, lineh)

    return $ Size (realToFrac (x2 - x1) / dpr) (realToFrac (y2 - y1) / dpr)

  computeGlyphsPos font fontSize fontSpaceH text = unsafePerformIO $ do
    setFont ctx dpr font fontSize fontSpaceH
    glyphs <- if text /= ""
      then fmTextGlyphPositions ctx 0 0 text
      else return Seq.empty

    return $ Seq.zipWith toGlyphPos (Seq.fromList (T.unpack text)) glyphs
    where
      toGlyphPos chr glyph = GlyphPos {
        _glpGlyph = chr,
        _glpXMin = realToFrac (glyphPosMinX glyph) / dpr,
        _glpXMax = realToFrac (glyphPosMaxX glyph) / dpr,
        _glpYMin = realToFrac (glyphPosMinY glyph) / dpr,
        _glpYMax = realToFrac (glyphPosMaxY glyph) / dpr,
        _glpW = realToFrac (glyphPosMaxX glyph - glyphPosMinX glyph) / dpr,
        _glpH = realToFrac (glyphPosMaxY glyph - glyphPosMinY glyph) / dpr
      }

loadFont :: FMContext -> [Text] -> FontDef -> IO [Text]
loadFont ctx fonts (FontDef name path) = do
  res <- fmCreateFont ctx name path
  if res >= 0
    then return $ path : fonts
    else putStrLn ("Failed to load font: " ++ T.unpack name) >> return fonts

setFont :: FMContext -> Double -> Font -> FontSize -> FontSpace -> IO ()
setFont ctx dpr (Font name) (FontSize size) (FontSpace spaceH) = do
  fmFontFace ctx name
  fmFontSize ctx $ realToFrac $ size * dpr
  fmTextLetterSpacing ctx $ realToFrac $ spaceH * dpr