{-# LANGUAGE TypeFamilies #-} {-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- | -- Module : Wumpus.Basic.Text.LRText -- Copyright : (c) Stephen Tetley 2010 -- License : BSD3 -- -- Maintainer : Stephen Tetley -- Stability : highly unstable -- Portability : GHC -- -- LRText monad - left-to-right text, with kerning. -- -- Note - because Wumpus has no access to the metrics data inside -- a font file, the default spacing is not good and it is -- expected that kerning will need to be added per-letter for -- variable width fonts. -- -- This module makes precise text spacing \*possible\* - it does -- not make it \*easy\*. -- -------------------------------------------------------------------------------- module Wumpus.Basic.Text.LRText ( TextM , runTextM , execTextM , kern , char , symb , symbi , symbEscInt ) where import Wumpus.Basic.Graphic import Wumpus.Basic.SafeFonts import Wumpus.Basic.Utils.HList import Wumpus.Core -- package: wumpus-core import Control.Applicative import Control.Monad import Data.Char ( chr ) import Data.Monoid -- Need a note in wumpus-core and here about space:preserve -- Note - if we have font change (e.g. to symbol font) then we -- have to generate more than one hkernline. -- -- If one wants to be really prissy about optimization, one could -- generate two simultaneous and overlayed lines - one in regular -- font, one in Symbol. -- -- Result should be a LocGraphic (so cannot do a trace as we go). -- -- Note - the the state tracks two kernlines: one for symbols, data St u = St { delta_chr :: !u , delta_sym :: !u , acc_chr :: H (KerningChar u) , acc_sym :: H (KerningChar u) } data Env u = Env { char_width :: !u , spacer_width :: !u } -- Note - unlike Turtle for example, Text is a monad not a -- transformer. -- -- The rationale for this is to avoid complications percolating -- from the Drawing monad. It Text were built over the Drawing -- monad what would it do on a font change, a colour change... -- -- That say Text must still be run /within/ the Drawing so it -- can take the initial font size, stroke colour etc. -- newtype TextM u a = TextM { getTextM :: Env u -> St u -> (a, St u) } type instance MonUnit (TextM u) = u instance Functor (TextM u) where fmap f mf = TextM $ \r s -> let (a,s') = getTextM mf r s in (f a,s') instance Applicative (TextM u) where pure a = TextM $ \_ s -> (a,s) mf <*> ma = TextM $ \r s -> let (f,s') = getTextM mf r s (a,s'') = getTextM ma r s' in (f a,s'') instance Monad (TextM u) where return a = TextM $ \_ s -> (a,s) m >>= k = TextM $ \r s -> let (a,s') = getTextM m r s in (getTextM . k) a r s' -- Note - post has to displace in the vertical to get the bottom -- line at the base line... runTextM :: (Num u, FromPtSize u, DrawingCtxM m, u ~ MonUnit m) => TextM u a -> m (a, LocGraphic u) runTextM ma = askCtx >>= \ctx -> let e = runDF ctx envZero in post $ getTextM ma e stZero where post (a,st) = let sG = updCtxSym $ mkHKern $ toListH $ acc_sym st cG = mkHKern $ toListH $ acc_chr st in return (a, sG `lgappend` cG) mkHKern [] = const mempty mkHKern xs = hkernline xs updCtxSym lg = localLG (fontface symbol) lg execTextM :: (Num u, FromPtSize u, DrawingCtxM m, u ~ MonUnit m) => TextM u a -> m (LocGraphic u) execTextM ma = liftM snd $ runTextM ma stZero :: Num u => St u stZero = St { delta_chr = 0 , delta_sym = 0 , acc_chr = emptyH , acc_sym = emptyH } envZero :: FromPtSize u => DrawingF (Env u) envZero = (\sz -> Env { char_width = fromPtSize $ charWidth sz , spacer_width = fromPtSize $ spacerWidth sz }) <$> asksDF (font_size . font_props) gets :: (St u -> a) -> TextM u a gets fn = TextM $ \_ s -> (fn s, s) charMove :: Num u => TextM u () charMove = TextM $ \(Env {char_width=cw, spacer_width=sw}) s -> let step_width = cw + sw d_sym = (delta_sym s) + step_width in ((), s { delta_chr = step_width, delta_sym = d_sym }) symbMove :: Num u => TextM u () symbMove = TextM $ \(Env {char_width=cw, spacer_width=sw}) s -> let step_width = cw + sw d_chr = (delta_chr s) + step_width in ((), s { delta_chr = d_chr, delta_sym = step_width }) snocSymb :: KerningChar u -> TextM u () snocSymb kc = TextM $ \_ s -> ((), upd s) where upd = (\s a -> s { acc_sym = a `snocH` kc}) <*> acc_sym snocChar :: KerningChar u -> TextM u () snocChar kc = TextM $ \_ s -> ((), upd s) where upd = (\s a -> s { acc_chr = a `snocH` kc}) <*> acc_chr kern :: Num u => u -> TextM u () kern dx = TextM $ \_ s -> ((), upd s) where upd = (\s a b -> s { delta_chr = a+dx, delta_sym = b+dx}) <*> delta_chr <*> delta_sym char :: Num u => Char -> TextM u () char ch = gets delta_chr >>= \u -> snocChar (kernchar u ch) >> charMove symb :: Num u => Char -> TextM u () symb sy = gets delta_sym >>= \u -> snocSymb (kernchar u sy) >> symbMove symbi :: Num u => Int -> TextM u () symbi i = symb (chr i) symbEscInt :: Num u => Int -> TextM u () symbEscInt i = gets delta_sym >>= \u -> snocSymb (kernEscInt u i) >> symbMove