{-# LANGUAGE CPP #-} module Graphics.Rasterific.Svg.RenderContext ( RenderContext( .. ) , LoadedElements( .. ) , loadedFonts , loadedImages , IODraw , ViewBox , toRadian , capOfSvg , joinOfSvg , stripUnits , boundingBoxLength , lineariseXLength , lineariseYLength , linearisePoint , lineariseLength , prepareTexture , documentOfPattern , fillAlphaCombine , fillMethodOfSvg , emTransform ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative( (<$>) ) import Data.Monoid( Monoid( .. ) ) #endif import Control.Monad.Trans.State.Strict( StateT ) import Codec.Picture( PixelRGBA8( .. ) ) import qualified Codec.Picture as CP import qualified Data.Foldable as F import qualified Data.Map as M import Data.Monoid( Last( .. ) ) import Control.Lens( Lens', lens ) import Graphics.Rasterific.Linear( (^-^) ) import qualified Graphics.Rasterific as R import qualified Graphics.Rasterific.Texture as RT import Graphics.Text.TrueType import Graphics.Svg.Types toRadian :: Floating a => a -> a toRadian v = v / 180 * pi data RenderContext = RenderContext { _initialViewBox :: (R.Point, R.Point) , _renderViewBox :: (R.Point, R.Point) , _renderDpi :: Int , _contextDefinitions :: M.Map String Element , _fontCache :: FontCache , _subRender :: Document -> IODraw (R.Drawing PixelRGBA8 ()) , _basePath :: FilePath } data LoadedElements = LoadedElements { _loadedFonts :: M.Map FilePath Font , _loadedImages :: M.Map FilePath (CP.Image PixelRGBA8) } instance Monoid LoadedElements where mempty = LoadedElements mempty mempty mappend (LoadedElements a b) (LoadedElements a' b') = LoadedElements (a `mappend` a') (b `mappend` b') loadedFonts :: Lens' LoadedElements (M.Map FilePath Font) loadedFonts = lens _loadedFonts (\a b -> a { _loadedFonts = b }) loadedImages :: Lens' LoadedElements (M.Map FilePath (CP.Image PixelRGBA8)) loadedImages = lens _loadedImages (\a b -> a { _loadedImages = b }) type IODraw = StateT LoadedElements IO type ViewBox = (R.Point, R.Point) capOfSvg :: DrawAttributes -> (R.Cap, R.Cap) capOfSvg attrs = case getLast $ _strokeLineCap attrs of Nothing -> (R.CapStraight 1, R.CapStraight 1) Just CapSquare -> (R.CapStraight 1, R.CapStraight 1) Just CapButt -> (R.CapStraight 0, R.CapStraight 0) Just CapRound -> (R.CapRound, R.CapRound) joinOfSvg :: DrawAttributes -> R.Join joinOfSvg attrs = case (getLast $ _strokeLineJoin attrs, getLast $ _strokeMiterLimit attrs) of (Nothing, _) -> R.JoinRound (Just JoinMiter, Just _) -> R.JoinMiter 0 (Just JoinMiter, Nothing) -> R.JoinMiter 0 (Just JoinBevel, _) -> R.JoinMiter 5 (Just JoinRound, _) -> R.JoinRound stripUnits :: RenderContext -> Number -> Number stripUnits ctxt = toUserUnit (_renderDpi ctxt) boundingBoxLength :: RenderContext -> DrawAttributes -> R.PlaneBound -> Number -> Float boundingBoxLength ctxt attr (R.PlaneBound mini maxi) = go where R.V2 actualWidth actualHeight = abs <$> (maxi ^-^ mini) two = 2 :: Int coeff = sqrt (actualWidth ^^ two + actualHeight ^^ two) / sqrt 2 :: Float go num = case num of Num n -> realToFrac n Em n -> emTransform attr $ realToFrac n Percent p -> realToFrac p * coeff _ -> go $ stripUnits ctxt num boundbingBoxLinearise :: RenderContext -> DrawAttributes -> R.PlaneBound -> Point -> R.Point boundbingBoxLinearise ctxt attr (R.PlaneBound mini@(R.V2 xi yi) maxi) (xp, yp) = R.V2 (finalX xp) (finalY yp) where R.V2 w h = abs <$> (maxi ^-^ mini) finalX nu = case nu of Num n -> realToFrac n Em n -> emTransform attr $ realToFrac n Percent p -> realToFrac p * w + xi _ -> finalX $ stripUnits ctxt nu finalY nu = case nu of Num n -> realToFrac n Em n -> emTransform attr $ realToFrac n Percent p -> realToFrac p * h + yi _ -> finalY $ stripUnits ctxt nu lineariseXLength :: RenderContext -> DrawAttributes -> Number -> Float lineariseXLength _ _ (Num i) = realToFrac i lineariseXLength _ attr (Em i) = emTransform attr $ realToFrac i lineariseXLength ctxt _ (Percent p) = abs (xe - xs) * realToFrac p where (R.V2 xs _, R.V2 xe _) = _renderViewBox ctxt lineariseXLength ctxt attr num = lineariseXLength ctxt attr $ stripUnits ctxt num lineariseYLength :: RenderContext -> DrawAttributes -> Number -> Float lineariseYLength _ _ (Num i) = realToFrac i lineariseYLength _ attr (Em n) = emTransform attr $ realToFrac n lineariseYLength ctxt _ (Percent p) = abs (ye - ys) * (realToFrac p) where (R.V2 _ ys, R.V2 _ ye) = _renderViewBox ctxt lineariseYLength ctxt attr num = lineariseYLength ctxt attr $ stripUnits ctxt num linearisePoint :: RenderContext -> DrawAttributes -> Point -> R.Point linearisePoint ctxt attr (p1, p2) = R.V2 (xs + lineariseXLength ctxt attr p1) (ys + lineariseYLength ctxt attr p2) where (R.V2 xs ys, _) = _renderViewBox ctxt emTransform :: DrawAttributes -> Float -> Float emTransform attr n = case getLast $ _fontSize attr of Nothing -> 16 * realToFrac n Just (Num v) -> realToFrac v * n Just _ -> 16 * n lineariseLength :: RenderContext -> DrawAttributes -> Number -> Float lineariseLength _ _ (Num i) = realToFrac i lineariseLength _ attr (Em i) = emTransform attr $ realToFrac i lineariseLength ctxt _ (Percent v) = realToFrac v * coeff where (R.V2 x1 y1, R.V2 x2 y2) = _renderViewBox ctxt actualWidth = abs $ x2 - x1 actualHeight = abs $ y2 - y1 two = 2 :: Int coeff = sqrt (actualWidth ^^ two + actualHeight ^^ two) / sqrt 2 lineariseLength ctxt attr num = lineariseLength ctxt attr $ stripUnits ctxt num prepareLinearGradientTexture :: RenderContext -> DrawAttributes -> LinearGradient -> Float -> [R.Primitive] -> R.Texture PixelRGBA8 prepareLinearGradientTexture ctxt attr grad opa prims = let bounds = F.foldMap R.planeBounds prims lineariser = case _linearGradientUnits grad of CoordUserSpace -> linearisePoint ctxt attr CoordBoundingBox -> boundbingBoxLinearise ctxt attr bounds gradient = [(offset, fillAlphaCombine opa color) | GradientStop offset color <- _linearGradientStops grad] startPoint = lineariser $ _linearGradientStart grad stopPoint = lineariser $ _linearGradientStop grad in RT.linearGradientTexture gradient startPoint stopPoint prepareRadialGradientTexture :: RenderContext -> DrawAttributes -> RadialGradient -> Float -> [R.Primitive] -> R.Texture PixelRGBA8 prepareRadialGradientTexture ctxt attr grad opa prims = let bounds = F.foldMap R.planeBounds prims (lineariser, lengthLinearise) = case _radialGradientUnits grad of CoordUserSpace -> (linearisePoint ctxt attr, lineariseLength ctxt attr) CoordBoundingBox -> (boundbingBoxLinearise ctxt attr bounds, boundingBoxLength ctxt attr bounds) gradient = [(offset, fillAlphaCombine opa color) | GradientStop offset color <- _radialGradientStops grad] center = lineariser $ _radialGradientCenter grad radius = lengthLinearise $ _radialGradientRadius grad in case (_radialGradientFocusX grad, _radialGradientFocusY grad) of (Nothing, Nothing) -> RT.radialGradientTexture gradient center radius (Just fx, Nothing) -> RT.radialGradientWithFocusTexture gradient center radius $ lineariser (fx, snd $ _radialGradientCenter grad) (Nothing, Just fy) -> RT.radialGradientWithFocusTexture gradient center radius $ lineariser (fst $ _radialGradientCenter grad, fy) (Just fx, Just fy) -> RT.radialGradientWithFocusTexture gradient center radius $ lineariser (fx, fy) fillMethodOfSvg :: DrawAttributes -> R.FillMethod fillMethodOfSvg attr = case getLast $ _fillRule attr of Nothing -> R.FillWinding Just FillNonZero -> R.FillWinding Just FillEvenOdd -> R.FillEvenOdd fillAlphaCombine :: Float -> PixelRGBA8 -> PixelRGBA8 fillAlphaCombine opacity (PixelRGBA8 r g b a) = PixelRGBA8 r g b alpha where a' = fromIntegral a / 255.0 alpha = floor . max 0 . min 255 $ opacity * a' * 255 documentOfPattern :: Pattern -> String -> Document documentOfPattern pat loc = Document { _viewBox = _patternViewBox pat , _width = return $ _patternWidth pat , _height = return $ _patternHeight pat , _elements = _patternElements pat , _definitions = M.empty , _styleRules = [] , _description = "" , _documentLocation = loc } prepareTexture :: RenderContext -> DrawAttributes -> Texture -> Float -> [R.Primitive] -> IODraw (Maybe (R.Texture PixelRGBA8)) prepareTexture _ _ FillNone _opacity _ = return Nothing prepareTexture _ _ (ColorRef color) opacity _ = return . Just . RT.uniformTexture $ fillAlphaCombine opacity color prepareTexture ctxt attr (TextureRef ref) opacity prims = maybe (return Nothing) prepare $ M.lookup ref (_contextDefinitions ctxt) where prepare (ElementGeometry _) = return Nothing prepare (ElementMarker _) = return Nothing prepare (ElementMask _) = return Nothing prepare (ElementClipPath _) = return Nothing prepare (ElementLinearGradient grad) = return . Just $ prepareLinearGradientTexture ctxt attr grad opacity prims prepare (ElementRadialGradient grad) = return . Just $ prepareRadialGradientTexture ctxt attr grad opacity prims prepare (ElementPattern pat) = do let doc = documentOfPattern pat (_basePath ctxt) dpi = _renderDpi ctxt w = floor . lineariseXLength ctxt attr $ _patternWidth pat h = floor . lineariseYLength ctxt attr $ _patternHeight pat patDrawing <- _subRender ctxt doc return . Just $ RT.patternTexture w h dpi (PixelRGBA8 0 0 0 0) patDrawing