{-# 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 horizontal text spacing \*possible\*, -- it does not make it \*easy\*. -- -------------------------------------------------------------------------------- module Wumpus.Basic.Text.LRText ( LRText , runLRText , execLRText , kern , char , escInt , escName , symb , symbEscInt , symbEscName ) 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 -- 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. -- -- Apropos /optimization/ we have two two simultaneous and -- overlayed lines - one in the regular font, and 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 LRText u a = LRText { getLRText :: Env u -> St u -> (a, St u) } type instance MonUnit (LRText u) = u instance Functor (LRText u) where fmap f mf = LRText $ \r s -> let (a,s') = getLRText mf r s in (f a,s') instance Applicative (LRText u) where pure a = LRText $ \_ s -> (a,s) mf <*> ma = LRText $ \r s -> let (f,s') = getLRText mf r s (a,s'') = getLRText ma r s' in (f a,s'') instance Monad (LRText u) where return a = LRText $ \_ s -> (a,s) m >>= k = LRText $ \r s -> let (a,s') = getLRText m r s in (getLRText . k) a r s' runLRText :: (Num u, FromPtSize u) => LRText u a -> LocImage u a runLRText ma = \pt -> envZero >>= \e1 -> let (a,st) = getLRText ma e1 st_zero in mkline pt (acc_chr st) >>= \g1 -> localize (fontface symbol) (mkline pt (acc_sym st)) >>= \g2 -> return (a, g1 `oplus` g2) where mkline pt h = hkernline (toListH h) pt execLRText :: (Num u, FromPtSize u) => LRText u a -> LocGraphic u execLRText ma = \pt -> liftM snd (runLRText ma pt) st_zero :: Num u => St u st_zero = St { delta_chr = 0 , delta_sym = 0 , acc_chr = emptyH , acc_sym = emptyH } envZero :: FromPtSize u => DrawingR (Env u) envZero = (\sz -> Env { char_width = fromPtSize $ charWidth sz , spacer_width = fromPtSize $ spacerWidth sz }) <$> fontSize gets :: (St u -> a) -> LRText u a gets fn = LRText $ \_ s -> (fn s, s) charMove :: Num u => LRText u () charMove = LRText $ \(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 => LRText u () symbMove = LRText $ \(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 -> LRText u () snocSymb kc = LRText $ \_ s -> ((), upd s) where upd = (\s a -> s { acc_sym = a `snocH` kc}) <*> acc_sym snocChar :: KerningChar u -> LRText u () snocChar kc = LRText $ \_ s -> ((), upd s) where upd = (\s a -> s { acc_chr = a `snocH` kc}) <*> acc_chr kern :: Num u => u -> LRText u () kern dx = LRText $ \_ 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 -> LRText u () char ch = gets delta_chr >>= \u -> snocChar (kernchar u ch) >> charMove escInt :: Num u => Int -> LRText u () escInt i = gets delta_chr >>= \u -> snocChar (kernEscInt u i) >> charMove escName :: Num u => String -> LRText u () escName s = gets delta_chr >>= \u -> snocChar (kernEscName u s) >> charMove symb :: Num u => Char -> LRText u () symb sy = gets delta_sym >>= \u -> snocSymb (kernchar u sy) >> symbMove symbEscInt :: Num u => Int -> LRText u () symbEscInt i = gets delta_sym >>= \u -> snocSymb (kernEscInt u i) >> symbMove symbEscName :: Num u => String -> LRText u () symbEscName s = gets delta_sym >>= \u -> snocSymb (kernEscName u s) >> symbMove