module Graphics.SVGFonts.Text
(
TextOpts(..)
, Mode(..)
, Spacing(..)
, textSVG
, textSVG'
, textSVG_
) where
import Data.Default.Class
import Diagrams.Prelude hiding (font, text)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Graphics.SVGFonts.Fonts (lin)
import Graphics.SVGFonts.ReadFont
import Graphics.SVGFonts.CharReference (characterStrings)
data TextOpts n = TextOpts
{ textFont :: PreparedFont n
, mode :: Mode
, spacing :: Spacing
, underline :: Bool
, textWidth :: n
, textHeight :: n
}
instance (Read n, RealFloat n) => Default (TextOpts n) where
def = TextOpts lin INSIDE_H KERN False 1 1
textSVG :: (Read n, RealFloat n) => String -> n -> Path V2 n
textSVG t h = textSVG' with { textHeight = h } t
textSVG' :: RealFloat n => TextOpts n -> String -> Path V2 n
textSVG' topts text =
case mode topts of
INSIDE_WH -> makeString (textHeight topts * sumh / maxY) (textHeight topts) (textWidth topts / (textHeight topts * sumh / maxY))
INSIDE_W -> makeString (textWidth topts) (textWidth topts * maxY / sumh) 1
INSIDE_H -> makeString (textHeight topts * sumh / maxY) (textHeight topts) 1
where
makeString w h space = (scaleY (h/maxY) $ scaleX (w/sumh) $
mconcat $
zipWith translate (horPos space)
(map polygonChar (zip str (adjusted_hs space))) ) # centerXY
(fontD,outl) = textFont topts
polygonChar (ch,a) = (fromMaybe mempty (Map.lookup ch outl)) <> (underlineChar a)
underlineChar a | underline topts = translateY ulinePos (rect a ulineThickness)
| otherwise = mempty
ulinePos = underlinePosition fontD
ulineThickness = underlineThickness fontD
horPos space = reverse $ added ( zero : (map (unitX ^*) (adjusted_hs space)) )
adjusted_hs space = map (*space) hs
hs = horizontalAdvances str fontD (isKern (spacing topts))
sumh = sum hs
added = snd.(foldl (\(h,l) (b,_) -> (h ^+^ b, (h ^+^ b):l))
(zero,[])). (map (\x->(x,[])))
maxY = bbox_dy fontD
ligatures = ((filter ((>1) . length)) . Map.keys . fontDataGlyphs) fontD
str = map T.unpack $ characterStrings text ligatures
textSVG_ :: forall b n. (TypeableFloat n, Renderable (Path V2 n) b) =>
TextOpts n -> String -> QDiagram b V2 n Any
textSVG_ topts text =
case mode topts of
INSIDE_WH -> makeString (textHeight topts * sumh / maxY) (textHeight topts) ((textWidth topts) / (textHeight topts * sumh / maxY))
INSIDE_W -> makeString (textWidth topts) (textWidth topts * maxY / sumh) 1
INSIDE_H -> makeString (textHeight topts * sumh / maxY) (textHeight topts) 1
where
makeString w h space =( ( translate (r2 (w*space/2,h/2)) $
scaleY (h/maxY) $ scaleX (w/sumh) $
translateY ( bbox_ly fontD) $
mconcat $
zipWith translate (horPos space)
(map polygonChar (zip str (adjusted_hs space))) ) # stroke # withEnvelope ((rect (w*space) h) :: D V2 n)
) # alignBL # translateY (bbox_ly fontD*h/maxY)
(fontD,outl) = (textFont topts)
polygonChar (ch,a) = (fromMaybe mempty (Map.lookup ch outl)) <> (underlineChar a)
underlineChar a | underline topts = translateX (a/2) $ translateY ulinePos (rect a ulineThickness)
| otherwise = mempty
ulinePos = underlinePosition fontD
ulineThickness = underlineThickness fontD
horPos space = reverse $ added ( zero : (map (unitX ^*) (adjusted_hs space)) )
hs = horizontalAdvances str fontD (isKern (spacing topts))
adjusted_hs space = map (*space) hs
sumh = sum hs
added = snd.(foldl (\(h,l) (b,_) -> (h ^+^ b, (h ^+^ b):l))
(zero,[])). (map (\x->(x,[])))
maxY = bbox_dy fontD
ligatures = (filter ((>1) . length) . Map.keys . fontDataGlyphs) fontD
str = map T.unpack $ characterStrings text ligatures
data Mode = INSIDE_H
| INSIDE_W
| INSIDE_WH
deriving Show
data Spacing = HADV
| KERN
deriving Show
isKern :: Spacing -> Bool
isKern KERN = True
isKern _ = False
horizontalAdvances :: RealFloat n => [String] -> FontData n -> Bool -> [n]
horizontalAdvances [] _ _ = []
horizontalAdvances [ch] fd _ = [horizontalAdvance ch fd]
horizontalAdvances (ch0:ch1:s) fd kerning = ((horizontalAdvance ch0 fd) (ka (fontDataKerning fd))) :
(horizontalAdvances (ch1:s) fd kerning)
where ka kern | kerning = (kernAdvance ch0 ch1 kern True) + (kernAdvance ch0 ch1 kern False)
| otherwise = 0