{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Geometry.Svg.Writer where
import Control.Lens hiding (rmap, Const(..))
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.Lazy.Char8 as B8
import Data.Ext
import Data.Fixed
import qualified Data.Foldable as F
import qualified Data.Geometry.Ipe.Attributes as IA
import Data.Geometry.Ipe.Color (IpeColor(..))
import Data.Geometry.Ipe.Types
import qualified Data.Geometry.Ipe.Types as Ipe
import Data.Geometry.Ipe.Value (IpeValue(..))
import Data.Geometry.Point
import Data.Geometry.PolyLine
import Data.Geometry.Polygon
import Data.Geometry.Svg.MathCoordinateSystem
import Data.Geometry.Transformation (Matrix)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Maybe
import Data.Monoid (mconcat)
import Data.Proxy
import Data.Ratio
import Data.Semigroup.Foldable (toNonEmpty)
import Data.Singletons (Apply)
import Data.Vinyl hiding (Label)
import Data.Vinyl.Functor
import Data.Vinyl.TypeLevel
import Text.Blaze (ToMarkup(toMarkup), ToValue(toValue))
import qualified Text.Blaze.Svg as Svg
import qualified Text.Blaze.Svg.Renderer.Utf8 as SvgRender
import Text.Blaze.Svg11 ((!))
import qualified Text.Blaze.Svg11 as Svg
import qualified Text.Blaze.Svg11.Attributes as A
toSvgXML :: ToMarkup t => t -> B.ByteString
toSvgXML = SvgRender.renderSvg
. Svg.docTypeSvg
. renderCanvas (createCanvas @Double 800 600) []
. svgO
printSvgXML :: ToMarkup t => t -> IO ()
printSvgXML = B8.putStrLn . toSvgXMLElem
svgO :: ToMarkup a => a -> Svg.Svg
svgO = Svg.toSvg
toSvgXMLElem :: ToMarkup t => t -> B.ByteString
toSvgXMLElem = SvgRender.renderSvg . Svg.toSvg
printSvgXMLElem :: ToMarkup t => t -> IO ()
printSvgXMLElem = B8.putStrLn . toSvgXMLElem
instance Real r => ToMarkup (IpeObject r) where
toMarkup (IpeGroup g) = toMarkup g
toMarkup (IpeImage i) = toMarkup i
toMarkup (IpeTextLabel t) = toMarkup t
toMarkup (IpeMiniPage m) = toMarkup m
toMarkup (IpeUse u) = toMarkup u
toMarkup (IpePath (p :+ ats)) = toMarkup $ p :+ (ats' <> ats)
where
ats' = IA.attr IA.SFill $ IpeColor $ Named "transparent"
instance ( ToMarkup g
, AllConstrained IpeToSvgAttr rs
, ReifyConstraint ToValue (IA.Attr f) rs
, RMap rs, RecordToList rs
, RecAll (IA.Attr f) rs ToValue
) => ToMarkup (g :+ IA.Attributes f rs) where
toMarkup (i :+ ats) = toMarkup i `applyAts` svgWriteAttrs ats
instance Real r => ToMarkup (TextLabel r) where
toMarkup (Label t p) = text_ p [] t
instance Real r => ToMarkup (MiniPage r) where
toMarkup (MiniPage t p w) = text_ p [A.width (toPValue w)] t
instance Real r => ToMarkup (Image r) where
toMarkup _ = error "ToMarkup: Image not implemented yet"
instance HasResolution p => ToValue (Fixed p) where
toValue = toAValue
instance Integral a => ToValue (Ratio a) where
toValue = toValue @Pico . realToFrac
instance Real r => ToValue (PathSegment r) where
toValue = \case
PolyLineSegment pl -> Svg.mkPath . toPath $ pl^.points.to toNonEmpty
PolygonPath pg -> Svg.mkPath $ do toPath $ pg^.outerBoundary.to toNonEmpty
Svg.z
EllipseSegment _ -> undefined
_ -> error "toValue: not implemented yet"
toPath :: Real r => NonEmpty (Point 2 r :+ p) -> Svg.Path
toPath pts = case (^.core) <$> pts of
(v:|vs) -> do Svg.m (showP $ v^.xCoord) (showP $ v^.yCoord)
mapM_ (\(Point2 x y) -> Svg.l (showP x) (showP y)) vs
instance Real r => ToMarkup (Ipe.Path r) where
toMarkup p = Svg.path ! A.d (toValue p)
instance Real r => ToValue (Path r) where
toValue (Path s) = mconcat . map toValue . F.toList $ s
instance Real r => ToMarkup (Ipe.IpeSymbol r) where
toMarkup (Symbol p _) = Svg.circle ! A.cx (toPValue $ p^.xCoord)
! A.cy (toPValue $ p^.yCoord)
! A.r (toPValue 5)
instance Real r => ToMarkup (Ipe.Group r) where
toMarkup (Group os) = Svg.g (mapM_ toMarkup os)
instance ToValue (Apply f at) => ToValue (IA.Attr f at) where
toValue att = maybe mempty toValue $ IA._getAttr att
applyAts :: Svg.Markup -> [(SvgF, Svg.AttributeValue)] -> Svg.Markup
applyAts x0 = F.foldl' (\x (f,v) -> x ! f v) x0
svgWriteAttrs :: ( AllConstrained IpeToSvgAttr rs
, RMap rs, RecordToList rs
, ReifyConstraint ToValue (IA.Attr f) rs
, RecAll (IA.Attr f) rs ToValue
)
=> IA.Attributes f rs
-> [(SvgF, Svg.AttributeValue)]
svgWriteAttrs (IA.Attrs r) = catMaybes . recordToList $ IA.zipRecsWith f (writeAttrFunctions r)
(writeAttrValues r)
where
f (Const mn) (Const mv) = Const $ (,) <$> mn <*> mv
writeAttrFunctions :: AllConstrained IpeToSvgAttr rs
=> Rec f rs
-> Rec (Const (Maybe SvgF)) rs
writeAttrFunctions RNil = RNil
writeAttrFunctions (x :& xs) = Const (write'' x) :& writeAttrFunctions xs
where
write'' :: forall f s. IpeToSvgAttr s => f s -> Maybe SvgF
write'' _ = attrSvg (Proxy :: Proxy s)
writeAttrValues :: ( ReifyConstraint ToValue (IA.Attr f) rs, RMap rs
, RecAll (IA.Attr f) rs ToValue)
=> Rec (IA.Attr f) rs -> Rec (Const (Maybe Svg.AttributeValue)) rs
writeAttrValues = rmap (\(Compose (Dict x)) -> Const $ toMaybeValue x)
. reifyConstraint @ToValue
toMaybeValue :: ToValue (IA.Attr f at) => IA.Attr f at -> Maybe Svg.AttributeValue
toMaybeValue a = case a of
IA.NoAttr -> Nothing
IA.Attr _ -> Just $ toValue a
type SvgF = Svg.AttributeValue -> Svg.Attribute
class IpeToSvgAttr (a :: IA.AttributeUniverse) where
attrSvg :: proxy a -> Maybe SvgF
instance IpeToSvgAttr IA.Layer where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Matrix where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Pin where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Transformations where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Stroke where attrSvg _ = Just A.stroke
instance IpeToSvgAttr IA.Fill where attrSvg _ = Just A.fill
instance IpeToSvgAttr IA.Pen where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Size where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Dash where attrSvg _ = Nothing
instance IpeToSvgAttr IA.LineCap where attrSvg _ = Just A.strokeLinecap
instance IpeToSvgAttr IA.LineJoin where attrSvg _ = Nothing
instance IpeToSvgAttr IA.FillRule where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Arrow where attrSvg _ = Nothing
instance IpeToSvgAttr IA.RArrow where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Opacity where attrSvg _ = Just A.opacity
instance IpeToSvgAttr IA.Tiling where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Gradient where attrSvg _ = Nothing
instance IpeToSvgAttr IA.Clip where attrSvg _ = Just A.clip
deriving instance ToValue LayerName
instance Real r => ToValue (IpeColor r) where
toValue (IpeColor c) = case c of
Named t -> toValue t
Valued v -> toAValue $ fmap showP v
instance Real r => ToValue (IA.IpePen r) where
toValue _ = mempty
instance Real r => ToValue (IA.IpeSize r) where
toValue _ = mempty
instance Real r => ToValue (IA.IpeArrow r) where
toValue _ = mempty
instance Real r => ToValue (IA.IpeDash r) where
toValue _ = mempty
instance Real r => ToValue (Matrix 3 3 r) where
toValue _ = mempty
instance ToValue IA.FillType where
toValue _ = mempty
instance ToValue IA.PinType where
toValue _ = mempty
instance ToValue IA.TransformationTypes where
toValue _ = mempty