Chart-diagrams-1.2.2: Graphics/Rendering/Chart/Backend/Diagrams.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
-- | The backend to render charts with the diagrams library.
module Graphics.Rendering.Chart.Backend.Diagrams
( runBackend
, runBackendR
, defaultEnv
, customFontEnv
, DEnv(..), DFont
-- * File Output Functons
, FileFormat(..)
, FileOptions(..)
, fo_size
, fo_format
, fo_customFonts
, renderableToFile
, cBackendToFile
-- * EPS Utility Functions
, cBackendToEPSFile
, renderableToEPSFile -- deprecated
, renderableToEPSFile' -- deprecated
-- * SVG Utility Functions
, cBackendToSVG
, cBackendToEmbeddedFontSVG
, renderableToSVG
, renderableToSVG'
, renderableToSVGFile -- deprecated
, renderableToSVGFile' -- deprecated
, renderableToSVGString
, renderableToSVGString'
-- * SVG Embedded Font Utility Functions
, renderableToEmbeddedFontSVG
, renderableToEmbeddedFontSVG'
, renderableToEmbeddedFontSVGFile -- deprecated
, renderableToEmbeddedFontSVGFile' -- deprecated
) where
import Data.Default.Class
import Data.Colour
import Data.Colour.SRGB
import Data.List (unfoldr)
import Data.Monoid
import Data.Traversable
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.ByteString.Lazy as BS
import qualified Data.Text as T
import Control.Lens(makeLenses)
import Control.Monad.Operational
import Control.Monad.State.Lazy
import Diagrams.Core.Transform ( Transformation(..) )
import Diagrams.Prelude
( Diagram
, R2, P2, T2
, r2, p2, unr2, unp2
, rad, (@@)
, Trail(..), Segment
, (.+^), (<->), (~~)
)
import qualified Diagrams.Prelude as D
import qualified Diagrams.TwoD as D2
import qualified Diagrams.TwoD.Arc as D2
import qualified Diagrams.TwoD.Text as D2
import qualified Diagrams.Backend.Postscript as DEPS
import qualified Diagrams.Backend.SVG as DSVG
import Text.Blaze.Svg.Renderer.Utf8 ( renderSvg )
import qualified Text.Blaze.Svg11 as Svg
import qualified Graphics.SVGFonts.CharReference as F
import qualified Graphics.SVGFonts.ReadFont as F
import Graphics.SVGFonts.WriteFont ( makeSvgFont )
import Graphics.Rendering.Chart.Backend as G
import Graphics.Rendering.Chart.Backend.Impl
import Graphics.Rendering.Chart.Backend.Types
import Graphics.Rendering.Chart.Geometry as G
import Graphics.Rendering.Chart.Drawing
import Graphics.Rendering.Chart.Renderable
import Paths_Chart_diagrams ( getDataFileName )
-- -----------------------------------------------------------------------
-- General purpose file output function
-- -----------------------------------------------------------------------
-- | The file output format:
-- EPS -> Embedded Postscript
-- SVG -> SVG with text rendered as stroked paths
-- SVG -> SVG with embedded font information and text rendered as text operations
data FileFormat = EPS
| SVG
| SVG_EMBEDDED
data FileOptions = FileOptions {
_fo_size :: (Double,Double),
_fo_format :: FileFormat,
_fo_customFonts :: M.Map (String, FontSlant, FontWeight) FilePath
}
-- | Generate an image file for the given renderable, at the specified path. Size, format,
-- and text rendering mode are all set through the `FileOptions` parameter.
renderableToFile :: FileOptions -> Renderable a -> FilePath -> IO (PickFn a)
renderableToFile fo r path = cBackendToFile fo cb path
where
cb = render r (_fo_size fo)
-- | Generate an image file for the given drawing instructions, at the specified path. Size and
-- format are set through the `FileOptions` parameter.
cBackendToFile :: FileOptions -> ChartBackend a -> FilePath -> IO a
cBackendToFile fo cb path = do
env <- customFontEnv vectorAlignmentFns w h (_fo_customFonts fo)
case _fo_format fo of
EPS -> do
cBackendToEPSFile cb env path
SVG -> do
let (svg, a) = cBackendToSVG cb env
BS.writeFile path (renderSvg svg)
return a
SVG_EMBEDDED -> do
let (svg,a) = cBackendToEmbeddedFontSVG cb env
BS.writeFile path (renderSvg svg)
return a
where
(w,h) = _fo_size fo
-- -----------------------------------------------------------------------
-- SVG Utility Functions
-- -----------------------------------------------------------------------
-- | Output the given renderable to a SVG file of the specifed size
-- (in points), to the specified file using the default environment.
{-# DEPRECATED renderableToSVGFile "use renderToFile" #-}
renderableToSVGFile :: Renderable a -> Double -> Double -> FilePath -> IO (PickFn a)
renderableToSVGFile r w h file = do
(svg, x) <- renderableToSVGString r w h
BS.writeFile file svg
return x
-- | Output the given renderable to a SVG file using the given environment.
{-# DEPRECATED renderableToSVGFile' "use renderToFile" #-}
renderableToSVGFile' :: Renderable a -> DEnv -> FilePath -> IO (PickFn a)
renderableToSVGFile' r env file = do
let (svg, x) = renderableToSVGString' r env
BS.writeFile file svg
return x
-- | Output the given renderable to a string containing a SVG of the specifed size
-- (in points) using the default environment.
renderableToSVGString :: Renderable a -> Double -> Double -> IO (BS.ByteString, PickFn a)
renderableToSVGString r w h = do
(svg, x) <- renderableToSVG r w h
return (renderSvg svg, x)
-- | Output the given renderable to a string containing a SVG using the given environment.
renderableToSVGString' :: Renderable a -> DEnv -> (BS.ByteString, PickFn a)
renderableToSVGString' r env =
let (svg, x) = renderableToSVG' r env
in (renderSvg svg, x)
-- | Output the given renderable as a SVG of the specifed size
-- (in points) using the default environment.
renderableToSVG :: Renderable a -> Double -> Double -> IO (Svg.Svg, PickFn a)
renderableToSVG r w h = do
env <- defaultEnv vectorAlignmentFns w h
return $ renderableToSVG' r env
-- | Output the given renderable as a SVG using the given environment.
renderableToSVG' :: Renderable a -> DEnv -> (Svg.Svg, PickFn a)
renderableToSVG' r env =
let (w, h) = envOutputSize env
(d, x) = runBackendR env r
svg = D.renderDia DSVG.SVG (DSVG.SVGOptions (D2.Dims w h) Nothing) d
in (svg, x)
-- -----------------------------------------------------------------------
-- SVG Embedded Font Utility Functions
-- -----------------------------------------------------------------------
-- | Output the given renderable to a SVG file of the specifed size
-- (in points), to the specified file using the default environment.
-- Font are embedded to save space.
{-# DEPRECATED renderableToEmbeddedFontSVGFile "use renderToFile" #-}
renderableToEmbeddedFontSVGFile :: Renderable a -> Double -> Double -> FilePath -> IO (PickFn a)
renderableToEmbeddedFontSVGFile r w h file = do
(svg, x) <- renderableToEmbeddedFontSVG r w h
BS.writeFile file $ renderSvg svg
return x
-- | Output the given renderable to a SVG file using the given environment.
-- Font are embedded to save space.
{-# DEPRECATED renderableToEmbeddedFontSVGFile' "use renderToFile" #-}
renderableToEmbeddedFontSVGFile' :: Renderable a -> DEnv -> FilePath -> IO (PickFn a)
renderableToEmbeddedFontSVGFile' r env file = do
let (svg, x) = renderableToEmbeddedFontSVG' r env
BS.writeFile file $ renderSvg svg
return x
-- | Output the given renderable as a SVG of the specifed size
-- (in points) using the default environment.
-- Font are embedded to save space.
renderableToEmbeddedFontSVG :: Renderable a -> Double -> Double -> IO (Svg.Svg, PickFn a)
renderableToEmbeddedFontSVG r w h = do
env <- defaultEnv vectorAlignmentFns w h
return $ renderableToEmbeddedFontSVG' r env
-- | Output the given renderable as a SVG using the given environment.
-- Font are embedded to save space.
renderableToEmbeddedFontSVG' :: Renderable a -> DEnv -> (Svg.Svg,PickFn a)
renderableToEmbeddedFontSVG' r env = cBackendToEmbeddedFontSVG (render r size) env
where
size = envOutputSize env
cBackendToEPSFile :: ChartBackend a -> DEnv -> FilePath -> IO a
cBackendToEPSFile cb env path = do
let (w, h) = envOutputSize env
(d, a) = runBackend env cb
psOpts = DEPS.PostscriptOptions path (D2.Dims w h) DEPS.EPS
D.renderDia DEPS.Postscript psOpts d
return a
cBackendToSVG :: ChartBackend a -> DEnv -> (Svg.Svg,a)
cBackendToSVG cb env = (svg,a)
where
(w, h) = envOutputSize env
(d, a) = runBackend env cb
svg = D.renderDia DSVG.SVG (DSVG.SVGOptions (D2.Dims w h) Nothing) d
cBackendToEmbeddedFontSVG :: ChartBackend a -> DEnv -> (Svg.Svg,a)
cBackendToEmbeddedFontSVG cb env = (svg, x)
where
(w, h) = envOutputSize env
(d, x, gs) = runBackendWithGlyphs env cb
fontDefs = Just $ forM_ (M.toList gs) $ \((fFam, fSlant, fWeight), usedGs) -> do
let fs = envFontStyle env
let font = envSelectFont env $ fs { _font_name = fFam
, _font_slant = fSlant
, _font_weight = fWeight
}
makeSvgFont font usedGs
-- M.Map (String, FontSlant, FontWeight) (S.Set String)
-- makeSvgFont :: (FontData, OutlineMap) -> Set.Set String -> S.Svg
svg = D.renderDia DSVG.SVG (DSVG.SVGOptions (D2.Dims w h) fontDefs) d
-- -----------------------------------------------------------------------
-- EPS Utility Functions
-- -----------------------------------------------------------------------
-- | Output the given renderable to a EPS file using the default environment.
{-# DEPRECATED renderableToEPSFile "use renderToFile" #-}
renderableToEPSFile :: Renderable a -> Double -> Double -> FilePath -> IO (PickFn a)
renderableToEPSFile r w h file = do
env <- defaultEnv vectorAlignmentFns w h
renderableToEPSFile' r env file
-- | Output the given renderable to a EPS file using the given environment.
{-# DEPRECATED renderableToEPSFile' "use renderToFile" #-}
renderableToEPSFile' :: Renderable a -> DEnv -> FilePath -> IO (PickFn a)
renderableToEPSFile' r env file = do
let (w, h) = envOutputSize env
let (d, x) = runBackendR env r
let psOpts = DEPS.PostscriptOptions
file
(D2.Dims w h)
DEPS.EPS
D.renderDia DEPS.Postscript psOpts d
return x
-- -----------------------------------------------------------------------
-- Backend
-- -----------------------------------------------------------------------
-- | The diagrams backend environement.
data DEnv = DEnv
{ envAlignmentFns :: AlignmentFns -- ^ The used alignment functions.
, envFontStyle :: FontStyle -- ^ The current/initial font style.
, envSelectFont :: FontStyle -> DFont -- ^ The font selection function.
, envOutputSize :: (Double, Double) -- ^ The size of the rendered output.
, envUsedGlyphs :: M.Map (String, FontSlant, FontWeight) (S.Set String)
-- ^ The map of all glyphs that are used from a specific font.
}
-- | A font a delivered by SVGFonts.
type DFont = (F.FontData, F.OutlineMap)
type DState a = State DEnv a
defaultFonts :: IO (FontStyle -> DFont)
defaultFonts = do
serifR <- loadDefaultFont "fonts/LinLibertine_R.svg"
serifRB <- loadDefaultFont "fonts/LinLibertine_RB.svg"
serifRBI <- loadDefaultFont "fonts/LinLibertine_RBI.svg"
serifRI <- loadDefaultFont "fonts/LinLibertine_RI.svg"
sansR <- loadDefaultFont "fonts/SourceSansPro_R.svg"
sansRB <- loadDefaultFont "fonts/SourceSansPro_RB.svg"
sansRBI <- loadDefaultFont "fonts/SourceSansPro_RBI.svg"
sansRI <- loadDefaultFont "fonts/SourceSansPro_RI.svg"
monoR <- loadDefaultFont "fonts/SourceCodePro_R.svg"
monoRB <- loadDefaultFont "fonts/SourceCodePro_RB.svg"
let selectFont :: FontStyle -> DFont
selectFont fs = case (_font_name fs, _font_slant fs, _font_weight fs) of
("serif", FontSlantNormal , FontWeightNormal) -> alterFontFamily "serif" serifR
("serif", FontSlantNormal , FontWeightBold ) -> alterFontFamily "serif" serifRB
("serif", FontSlantItalic , FontWeightNormal) -> alterFontFamily "serif" serifRI
("serif", FontSlantOblique, FontWeightNormal) -> alterFontFamily "serif" serifRI
("serif", FontSlantItalic , FontWeightBold ) -> alterFontFamily "serif" serifRBI
("serif", FontSlantOblique, FontWeightBold ) -> alterFontFamily "serif" serifRBI
("sans-serif", FontSlantNormal , FontWeightNormal) -> alterFontFamily "sans-serif" sansR
("sans-serif", FontSlantNormal , FontWeightBold ) -> alterFontFamily "sans-serif" sansRB
("sans-serif", FontSlantItalic , FontWeightNormal) -> alterFontFamily "sans-serif" sansRI
("sans-serif", FontSlantOblique, FontWeightNormal) -> alterFontFamily "sans-serif" sansRI
("sans-serif", FontSlantItalic , FontWeightBold ) -> alterFontFamily "sans-serif" sansRBI
("sans-serif", FontSlantOblique, FontWeightBold ) -> alterFontFamily "sans-serif" sansRBI
("monospace", _, FontWeightNormal) -> alterFontFamily "monospace" monoR
("monospace", _, FontWeightBold ) -> alterFontFamily "monospace" monoRB
(fam, FontSlantNormal , FontWeightNormal) | fam `isFontFamily` serifR -> serifR
(fam, FontSlantNormal , FontWeightBold ) | fam `isFontFamily` serifRB -> serifRB
(fam, FontSlantItalic , FontWeightNormal) | fam `isFontFamily` serifRI -> serifRI
(fam, FontSlantOblique, FontWeightNormal) | fam `isFontFamily` serifRI -> serifRI
(fam, FontSlantItalic , FontWeightBold ) | fam `isFontFamily` serifRBI -> serifRBI
(fam, FontSlantOblique, FontWeightBold ) | fam `isFontFamily` serifRBI -> serifRBI
(fam, FontSlantNormal , FontWeightNormal) | fam `isFontFamily` sansR -> sansR
(fam, FontSlantNormal , FontWeightBold ) | fam `isFontFamily` sansRB -> sansRB
(fam, FontSlantItalic , FontWeightNormal) | fam `isFontFamily` sansRI -> sansRI
(fam, FontSlantOblique, FontWeightNormal) | fam `isFontFamily` sansRI -> sansRI
(fam, FontSlantItalic , FontWeightBold ) | fam `isFontFamily` sansRBI -> sansRBI
(fam, FontSlantOblique, FontWeightBold ) | fam `isFontFamily` sansRBI -> sansRBI
(fam, _, FontWeightNormal) | fam `isFontFamily` monoR -> monoR
(fam, _, FontWeightBold ) | fam `isFontFamily` monoRB -> monoRB
(_, slant, weight) -> selectFont (fs { _font_name = "sans-serif" })
return selectFont
alterFontFamily :: String -> DFont -> DFont
alterFontFamily n (fd, om) = (fd { F.fontDataFamily = n }, om)
isFontFamily :: String -> DFont -> Bool
isFontFamily n (fd, _) = n == F.fontDataFamily fd
loadDefaultFont :: FilePath -> IO DFont
loadDefaultFont file = getDataFileName file >>= return . F.outlMap
loadFont :: FilePath -> IO DFont
loadFont = return . F.outlMap
-- | Produce an environment with a custom set of fonts.
-- The defult fonts are still loaded as fall back.
customFontEnv :: AlignmentFns -- ^ Alignment functions to use.
-> Double -- ^ The output image width in backend coordinates.
-> Double -- ^ The output image height in backend coordinates.
-> M.Map (String, FontSlant, FontWeight) FilePath -> IO DEnv
customFontEnv alignFns w h fontFiles = do
fonts <- traverse loadFont fontFiles
selectFont <- defaultFonts
return $ DEnv
{ envAlignmentFns = alignFns
, envFontStyle = def
, envSelectFont = \fs ->
case M.lookup (_font_name fs, _font_slant fs, _font_weight fs) fonts of
Just font -> font
Nothing -> selectFont fs
, envOutputSize = (w,h)
, envUsedGlyphs = M.empty
}
-- | Produce a default environment with the default fonts.
defaultEnv :: AlignmentFns -- ^ Alignment functions to use.
-> Double -- ^ The output image width in backend coordinates.
-> Double -- ^ The output image height in backend coordinates.
-> IO DEnv
defaultEnv alignFns w h = customFontEnv alignFns w h M.empty
-- | Run this backends renderer.
runBackendR :: (D.Backend b R2, D.Renderable (D.Path R2) b)
=> DEnv -- ^ Environment to start rendering with.
-> Renderable a -- ^ Chart render code.
-> (Diagram b R2, PickFn a) -- ^ The diagram.
runBackendR env r =
let cb = render r (envOutputSize env)
in runBackend env cb
-- | Run this backends renderer.
runBackend :: (D.Backend b R2, D.Renderable (D.Path R2) b)
=> DEnv -- ^ Environment to start rendering with.
-> ChartBackend a -- ^ Chart render code.
-> (Diagram b R2, a) -- ^ The diagram.
runBackend env m =
let (d, x) = evalState (runBackend' TextRenderSvg $ withDefaultStyle m) env
in (adjustOutputDiagram env d, x)
-- | Run this backends renderer.
runBackendWithGlyphs :: ( D.Backend b R2
, D.Renderable (D.Path R2) b
, D.Renderable (D2.Text) b)
=> DEnv -- ^ Environment to start rendering with.
-> ChartBackend a -- ^ Chart render code.
-> ( Diagram b R2, a
, M.Map (String, FontSlant, FontWeight) (S.Set String))
runBackendWithGlyphs env m =
let ((d, x), env') = runState (runBackend' TextRenderNative $ withDefaultStyle m) env
in (adjustOutputDiagram env d, x, envUsedGlyphs env')
-- | Flag to decide which technique should ne used to render text.
-- The type parameter is the primitive that has to be supported by
-- a backend when rendering text using this technique.
data TextRender a where
TextRenderNative :: TextRender (D2.Text)
TextRenderSvg :: TextRender (D.Path R2)
runBackend' :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> ChartBackend a -> DState (Diagram b R2, a)
runBackend' tr m = eval tr $ view $ m
where
eval :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> ProgramView ChartBackendInstr a -> DState (Diagram b R2, a)
eval tr (Return v) = return (mempty, v)
eval tr (StrokePath p :>>= f) = dStrokePath p <># step tr f
eval tr (FillPath p :>>= f) = dFillPath p <># step tr f
eval tr@TextRenderSvg (DrawText p s :>>= f) = dDrawTextSvg p s <># step tr f
eval tr@TextRenderNative (DrawText p s :>>= f) = dDrawTextNative p s <># step tr f
eval tr (GetTextSize s :>>= f) = dTextSize s <>= step tr f
eval tr (GetAlignments :>>= f) = dAlignmentFns <>= step tr f
eval tr (WithTransform m p :>>= f) = dWithTransform tr m p <>= step tr f
eval tr (WithFontStyle fs p :>>= f) = dWithFontStyle tr fs p <>= step tr f
eval tr (WithFillStyle fs p :>>= f) = dWithFillStyle tr fs p <>= step tr f
eval tr (WithLineStyle ls p :>>= f) = dWithLineStyle tr ls p <>= step tr f
eval tr (WithClipRegion r p :>>= f) = dWithClipRegion tr r p <>= step tr f
step :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> (v -> ChartBackend a) -> v -> DState (Diagram b R2, a)
step tr f v = runBackend' tr (f v)
(<>#) :: (Monad s, Monoid m) => s m -> (() -> s (m, a)) -> s (m, a)
(<>#) m f = do
ma <- m
return (ma, ()) <>= f
(<>=) :: (Monad s, Monoid m) => s (m, a) -> (a -> s (m, b)) -> s (m, b)
(<>=) m f = do
(ma, a) <- m
(mb, b) <- f a
return (mb <> ma, b)
-- | Executes the given state locally, but preserves the changes to the 'envUsedGlyphs'
-- map. Assumes that values are never removed from the map inbetween.
dLocal :: DState a -> DState a
dLocal m = do
env <- get
x <- m
env' <- get
put $ env { envUsedGlyphs = envUsedGlyphs env' }
return x
dStrokePath :: (D.Renderable (D.Path R2) b)
=> Path -> DState (Diagram b R2)
dStrokePath p = return $ applyFillStyle noFillStyle $ D.stroke $ convertPath False p
dFillPath :: (D.Renderable (D.Path R2) b)
=> Path -> DState (Diagram b R2)
dFillPath p = return $ applyLineStyle noLineStyle $ D.stroke $ convertPath True p
dTextSize :: (D.Renderable (D.Path R2) b)
=> String -> DState (Diagram b R2, TextSize)
dTextSize text = do
env <- get
let fs = envFontStyle env
let (scaledH, scaledA, scaledD, scaledYB) = calcFontMetrics env
return (mempty, TextSize
{ textSizeWidth = D2.width $ F.textSVG'
$ fontStyleToTextOpts env text
, textSizeAscent = scaledA -- scaledH * (a' / h') -- ascent
, textSizeDescent = scaledD -- scaledH * (d' / h') -- descent
, textSizeYBearing = scaledYB -- -scaledH * (capHeight / h)
, textSizeHeight = _font_size $ fs
})
dAlignmentFns :: (D.Renderable (D.Path R2) b)
=> DState (Diagram b R2, AlignmentFns)
dAlignmentFns = do
env <- get
return (mempty, envAlignmentFns env)
dDrawTextSvg :: (D.Renderable (D.Path R2) b)
=> Point -> String -> DState (Diagram b R2)
dDrawTextSvg (Point x y) text = do
env <- get
return $ D.transform (toTransformation $ translate (Vector x y) 1)
$ applyFontStyleSVG (envFontStyle env)
$ D2.scaleY (-1)
$ F.textSVG_ (fontStyleToTextOpts env text)
dDrawTextNative :: (D.Renderable D2.Text b)
=> Point -> String -> DState (Diagram b R2)
dDrawTextNative (Point x y) text = do
env <- get
addGlyphsOfString text
return $ D.transform (toTransformation $ translate (Vector x y) 1)
$ applyFontStyleText (envFontStyle env)
$ D2.scaleY (-1)
$ D2.baselineText text
dWith :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> (DEnv -> DEnv) -> (Diagram b R2 -> Diagram b R2)
-> ChartBackend a -> DState (Diagram b R2, a)
dWith tr envF dF m = dLocal $ do
modify envF
(ma, a) <- runBackend' tr m
return (dF ma, a)
dWithTransform :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> Matrix -> ChartBackend a -> DState (Diagram b R2, a)
dWithTransform tr t = dWith tr id $ D.transform (toTransformation t)
dWithLineStyle :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> LineStyle -> ChartBackend a -> DState (Diagram b R2, a)
dWithLineStyle tr ls = dWith tr id $ applyLineStyle ls
dWithFillStyle :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> FillStyle -> ChartBackend a -> DState (Diagram b R2, a)
dWithFillStyle tr fs = dWith tr id $ applyFillStyle fs
dWithFontStyle :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> FontStyle -> ChartBackend a -> DState (Diagram b R2, a)
dWithFontStyle tr fs = dWith tr (\e -> e { envFontStyle = fs }) $ id
dWithClipRegion :: (D.Renderable (D.Path R2) b, D.Renderable t b)
=> TextRender t -> Rect -> ChartBackend a -> DState (Diagram b R2, a)
dWithClipRegion tr clip = dWith tr id $ D2.clipBy (convertPath True $ rectPath clip)
-- -----------------------------------------------------------------------
-- Converions Helpers
-- -----------------------------------------------------------------------
addGlyphsOfString :: String -> DState ()
addGlyphsOfString s = do
env <- get
let fs = envFontStyle env
let fontData = fst $ envSelectFont env fs
let ligatures = ((filter ((>1) . length)) . (M.keys) . F.fontDataGlyphs) fontData
let glyphs = fmap T.unpack $ F.characterStrings s ligatures
modify $ \env ->
let gKey = (_font_name fs, _font_slant fs, _font_weight fs)
gMap = envUsedGlyphs env
entry = case M.lookup gKey gMap of
Nothing -> S.fromList glyphs
Just gs -> gs `S.union` S.fromList glyphs
in env { envUsedGlyphs = M.insert gKey entry gMap }
return ()
pointToP2 :: Point -> P2
pointToP2 (Point x y) = p2 (x,y)
adjustOutputDiagram :: (D.Backend b R2) => DEnv -> Diagram b R2 -> Diagram b R2
adjustOutputDiagram env d = D2.reflectY $ D2.view (p2 (0,0)) (r2 (envOutputSize env)) d
noLineStyle :: LineStyle
noLineStyle = def
{ _line_width = 0
, _line_color = transparent
}
noFillStyle :: FillStyle
noFillStyle = solidFillStyle transparent
toTransformation :: Matrix -> T2
toTransformation m = Transformation
(applyWithoutTrans m <-> applyWithoutTrans (invert m))
(applyWithoutTrans (transpose m) <-> applyWithoutTrans (transpose (invert m)))
(r2 (x0 m, y0 m))
transpose :: Matrix -> Matrix
transpose (Matrix xx yx xy yy _ _) = Matrix xx xy yx yy 0 0
-- | Apply a given affine transformation to a vector.
applyTransformation :: Matrix -> P2 -> P2
applyTransformation m p =
let (x,y) = D2.unp2 p
in p2 ( xx m * x + xy m * y + x0 m
, yx m * x + yy m * y + y0 m
)
-- | Apply a given affine transformation to a vector.
applyWithoutTrans :: Matrix -> R2 -> R2
applyWithoutTrans m v =
let (x,y) = D2.unr2 v
in r2 ( xx m * x + xy m * y
, yx m * x + yy m * y
)
-- | Apply the Chart line style to a diagram.
applyLineStyle :: (D.HasStyle a) => LineStyle -> a -> a
applyLineStyle ls = D.lineWidth (_line_width ls)
. D.lineColor (_line_color ls)
. D.lineCap (convertLineCap $ _line_cap ls)
. D.lineJoin (convertLineJoin $ _line_join ls)
. D.dashing (_line_dashes ls) 0
-- | Apply the Chart fill style to a diagram.
applyFillStyle :: (D.HasStyle a) => FillStyle -> a -> a
applyFillStyle fs = case fs of
FillStyleSolid cl -> D.fillColor cl
-- | Apply all pure diagrams properties from the font style.
applyFontStyleSVG :: (D.HasStyle a) => FontStyle -> a -> a
applyFontStyleSVG fs = applyLineStyle noLineStyle
. applyFillStyle (solidFillStyle $ _font_color fs)
applyFontStyleText :: (D.HasStyle a) => FontStyle -> a -> a
applyFontStyleText fs = D2.font (_font_name fs)
. D2.fontSize (_font_size fs)
. D2.fontSlant (convertFontSlant $ _font_slant fs)
. D2.fontWeight (convertFontWeight $ _font_weight fs)
. D.fillColor (_font_color fs)
-- | Calculate the font metrics for the currently set font style.
-- The returned value will be @(height, ascent, descent, ybearing)@.
calcFontMetrics :: DEnv -> (Double, Double, Double, Double)
calcFontMetrics env =
let fs = envFontStyle env
font@(fontData,_) = envSelectFont env fs
bbox = F.fontDataBoundingBox fontData
capHeight = F.fontDataCapHeight fontData
a = bbox !! 3
d = -bbox !! 1
h = unscaledH
a' = unscaledH
d' = (d / h) * h'
h' = (a + d) / (1 - d / h)
unscaledH = F.bbox_dy $ fontData
scaledHeight = _font_size fs * (h' / h)
scaledAscent = scaledHeight * (a' / h')
scaledDescent = scaledHeight * (d' / h')
scaledMaxHAdv = -scaledHeight * (capHeight / h)
in (scaledHeight, scaledAscent, scaledDescent, scaledMaxHAdv)
fontStyleToTextOpts :: DEnv -> String -> F.TextOpts
fontStyleToTextOpts env text =
let fs = envFontStyle env
font = envSelectFont env fs
(scaledH, _, _, _) = calcFontMetrics env
in F.TextOpts
{ F.txt = text
, F.fdo = font
, F.mode = F.INSIDE_H
, F.spacing = F.KERN
, F.underline = False
, F.textWidth = 1
, F.textHeight = scaledH -- _font_size fs
}
fontFromName :: String -> (F.FontData, F.OutlineMap)
fontFromName name = case name of
"serif" -> F.lin
"monospace" -> F.bit
_ -> F.lin
-- | Convert line caps.
convertLineCap :: LineCap -> D.LineCap
convertLineCap cap = case cap of
LineCapButt -> D.LineCapButt
LineCapRound -> D.LineCapRound
LineCapSquare -> D.LineCapSquare
-- | Convert line joins.
convertLineJoin :: LineJoin -> D.LineJoin
convertLineJoin join = case join of
LineJoinMiter -> D.LineJoinMiter
LineJoinRound -> D.LineJoinRound
LineJoinBevel -> D.LineJoinBevel
convertFontSlant :: FontSlant -> D2.FontSlant
convertFontSlant fs = case fs of
FontSlantNormal -> D2.FontSlantNormal
FontSlantItalic -> D2.FontSlantItalic
FontSlantOblique -> D2.FontSlantOblique
convertFontWeight :: FontWeight -> D2.FontWeight
convertFontWeight fw = case fw of
FontWeightBold -> D2.FontWeightBold
FontWeightNormal -> D2.FontWeightNormal
-- | Convert paths. The boolean says wether all trails
-- of the path shall be closed or remain open.
convertPath :: Bool -> Path -> D.Path R2
convertPath closeAll path =
let (start, t, restM) = pathToTrail closeAll (Point 0 0) $ makeLinesExplicit path
in D.pathFromTrailAt t start <> case restM of
Nothing -> mempty
Just rest -> convertPath closeAll rest
pathToTrail :: Bool -> Point -> Path
-> (D.Point R2, Trail R2, Maybe Path)
pathToTrail closeAll _ (MoveTo p0 path) =
let (t, close, rest) = pathToTrail' closeAll path p0
in (pointToP2 p0, makeTrail close t, rest)
pathToTrail closeAll _ path@(Arc c r s _ _) =
let p0 = translateP (pointToVec c) $ rotateP s $ Point r 0
(t, close, rest) = pathToTrail' closeAll path p0
in (pointToP2 p0, makeTrail close t, rest)
pathToTrail closeAll _ path@(ArcNeg c r s _ _) =
let p0 = translateP (pointToVec c) $ rotateP s $ Point r 0
(t, close, rest) = pathToTrail' closeAll path p0
in (pointToP2 p0, makeTrail close t, rest)
pathToTrail closeAll start path =
let (t, close, rest) = pathToTrail' closeAll path start
in (pointToP2 start, makeTrail close t, rest)
makeTrail :: Bool -> D.Trail' D.Line R2 -> Trail R2
makeTrail True t = D.wrapTrail $ D.closeLine t
makeTrail False t = D.wrapTrail $ t
pathToTrail' :: Bool -> Path -> Point -> (D.Trail' D.Line R2, Bool, Maybe Path)
pathToTrail' closeAll p@(MoveTo _ _) _ = (mempty, False || closeAll, Just p)
pathToTrail' closeAll (LineTo p1 path) p0 =
let (t, c, rest) = pathToTrail' closeAll path p1
in ( (pointToP2 p0 ~~ pointToP2 p1) <> t, c || closeAll, rest )
pathToTrail' closeAll (Arc p0 r s e path) _ =
let endP = translateP (pointToVec p0) $ rotateP e $ Point r 0
(t, c, rest) = pathToTrail' closeAll path endP
arcTrail = D2.scale r $ D2.arc (s @@ rad) (e @@ rad)
in ( arcTrail <> t, c || closeAll, rest )
pathToTrail' closeAll (ArcNeg p0 r s e path) _ =
let endP = translateP (pointToVec p0) $ rotateP e $ Point r 0
(t, c, rest) = pathToTrail' closeAll path endP
arcTrail = D2.scale r $ D2.arcCW (s @@ rad) (e @@ rad)
in ( arcTrail <> t, c || closeAll, rest )
pathToTrail' closeAll End _ = (mempty, False || closeAll, Nothing)
pathToTrail' closeAll Close _ = (mempty, True || closeAll, Nothing)
----------------------------------------------------------------------
$( makeLenses ''FileOptions )