module Data.Geometry.Ipe.Writer where
import Control.Lens ((^.),(^..),(.~),(&), to)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
import Data.Ext
import Data.Fixed
import qualified Data.Foldable as F
import Data.Geometry.Box
import Data.Geometry.Ipe.Attributes
import qualified Data.Geometry.Ipe.Attributes as IA
import Data.Geometry.Ipe.Types
import qualified Data.Geometry.Ipe.Types as IT
import Data.Geometry.LineSegment
import Data.Geometry.Point
import Data.Geometry.PolyLine
import Data.Geometry.Polygon (SimplePolygon, outerBoundary)
import qualified Data.Geometry.Transformation as GT
import Data.Geometry.Vector
import Data.Maybe (catMaybes, mapMaybe, fromMaybe)
import Data.Proxy
import Data.Ratio
import Data.Semigroup
import qualified Data.Seq2 as S2
import Data.Singletons
import qualified Data.Text as T
import qualified Data.Text as Text
import Data.Text (Text)
import qualified Data.Traversable as Tr
import Data.Vinyl
import Data.Vinyl.Functor
import Data.Vinyl.TypeLevel
import System.IO (hPutStrLn,stderr)
import Text.XML.Expat.Format (format')
import Text.XML.Expat.Tree
writeIpeFile :: IpeWriteText r => FilePath -> IpeFile r -> IO ()
writeIpeFile = flip writeIpeFile'
writeIpePage :: IpeWriteText r => FilePath -> IpePage r -> IO ()
writeIpePage fp = writeIpeFile fp . singlePageFile
printAsIpeSelection :: IpeWrite t => t -> IO ()
printAsIpeSelection = C.putStrLn . fromMaybe "" . toIpeSelectionXML
toIpeSelectionXML :: IpeWrite t => t -> Maybe B.ByteString
toIpeSelectionXML = fmap (format' . ipeSelection) . ipeWrite
where
ipeSelection x = Element "ipeselection" [] [x]
toIpeXML :: IpeWrite t => t -> Maybe B.ByteString
toIpeXML = fmap format' . ipeWrite
writeIpeFile' :: IpeWrite t => t -> FilePath -> IO ()
writeIpeFile' i fp = maybe err (B.writeFile fp) . toIpeXML $ i
where
err = hPutStrLn stderr $
"writeIpeFile: error converting to xml. File '" <> fp <> "'not written"
class IpeWriteText t where
ipeWriteText :: t -> Maybe Text
class IpeWrite t where
ipeWrite :: t -> Maybe (Node Text Text)
instance IpeWriteText (Apply f at) => IpeWriteText (Attr f at) where
ipeWriteText att = _getAttr att >>= ipeWriteText
ipeWriteAttrs :: ( AllSatisfy IpeAttrName rs
, RecAll (Attr f) rs IpeWriteText
) => IA.Attributes f rs -> [(Text,Text)]
ipeWriteAttrs (Attrs r) = catMaybes . recordToList $ zipRecsWith f (writeAttrNames r)
(writeAttrValues r)
where
f (Const n) (Const mv) = Const $ (n,) <$> mv
writeAttrValues :: RecAll f rs IpeWriteText => Rec f rs -> Rec (Const (Maybe Text)) rs
writeAttrValues = rmap (\(Compose (Dict x)) -> Const $ ipeWriteText x)
. reifyConstraint (Proxy :: Proxy IpeWriteText)
instance IpeWriteText Text where
ipeWriteText = Just
addAtts :: Node Text Text -> [(Text,Text)] -> Node Text Text
n `addAtts` ats = n { eAttributes = ats ++ eAttributes n }
mAddAtts :: Maybe (Node Text Text) -> [(Text, Text)] -> Maybe (Node Text Text)
mn `mAddAtts` ats = fmap (`addAtts` ats) mn
instance IpeWriteText Double where
ipeWriteText = writeByShow
instance IpeWriteText Int where
ipeWriteText = writeByShow
instance IpeWriteText Integer where
ipeWriteText = writeByShow
instance HasResolution p => IpeWriteText (Fixed p) where
ipeWriteText = writeByShow
instance Integral a => IpeWriteText (Ratio a) where
ipeWriteText = ipeWriteText . f . fromRational . toRational
where
f :: Pico -> Pico
f = id
writeByShow :: Show t => t -> Maybe Text
writeByShow = ipeWriteText . T.pack . show
unwords' :: [Maybe Text] -> Maybe Text
unwords' = fmap T.unwords . sequence
unlines' :: [Maybe Text] -> Maybe Text
unlines' = fmap T.unlines . sequence
instance IpeWriteText r => IpeWriteText (Point 2 r) where
ipeWriteText (Point2 x y) = unwords' [ipeWriteText x, ipeWriteText y]
instance IpeWriteText v => IpeWriteText (IpeValue v) where
ipeWriteText (Named t) = ipeWriteText t
ipeWriteText (Valued v) = ipeWriteText v
instance IpeWriteText TransformationTypes where
ipeWriteText Affine = Just "affine"
ipeWriteText Rigid = Just "rigid"
ipeWriteText Translations = Just "translations"
instance IpeWriteText PinType where
ipeWriteText No = Nothing
ipeWriteText Yes = Just "yes"
ipeWriteText Horizontal = Just "h"
ipeWriteText Vertical = Just "v"
deriving instance IpeWriteText r => IpeWriteText (IpeSize r)
deriving instance IpeWriteText r => IpeWriteText (IpePen r)
deriving instance IpeWriteText IpeColor
instance IpeWriteText r => IpeWriteText (IpeDash r) where
ipeWriteText (DashNamed t) = Just t
ipeWriteText (DashPattern xs x) = (\ts t -> mconcat [ "["
, Text.intercalate " " ts
, "] ", t ])
<$> Tr.mapM ipeWriteText xs
<*> ipeWriteText x
instance IpeWriteText FillType where
ipeWriteText Wind = Just "wind"
ipeWriteText EOFill = Just "eofill"
instance IpeWriteText r => IpeWriteText (IpeArrow r) where
ipeWriteText (IpeArrow n s) = (\n' s' -> n' <> "/" <> s') <$> ipeWriteText n
<*> ipeWriteText s
instance IpeWriteText r => IpeWriteText (Path r) where
ipeWriteText = fmap concat' . Tr.sequence . fmap ipeWriteText . _pathSegments
where
concat' = F.foldr1 (\t t' -> t <> "\n" <> t')
instance IpeWriteText r => IpeWrite (IpeSymbol r) where
ipeWrite (Symbol p n) = f <$> ipeWriteText p
where
f ps = Element "use" [ ("pos", ps)
, ("name", n)
] []
instance IpeWriteText r => IpeWriteText (GT.Matrix 3 3 r) where
ipeWriteText (GT.Matrix m) = unwords' [a,b,c,d,e,f]
where
(Vector3 r1 r2 _) = m
(Vector3 a c e) = ipeWriteText <$> r1
(Vector3 b d f) = ipeWriteText <$> r2
instance IpeWriteText r => IpeWriteText (Operation r) where
ipeWriteText (MoveTo p) = unwords' [ ipeWriteText p, Just "m"]
ipeWriteText (LineTo p) = unwords' [ ipeWriteText p, Just "l"]
ipeWriteText (CurveTo p q r) = unwords' [ ipeWriteText p
, ipeWriteText q
, ipeWriteText r, Just "m"]
ipeWriteText (Ellipse m) = unwords' [ ipeWriteText m, Just "e"]
ipeWriteText ClosePath = Just "h"
instance IpeWriteText r => IpeWriteText (PolyLine 2 () r) where
ipeWriteText pl = case pl^..points.Tr.traverse.core of
(p : rest) -> unlines' . map ipeWriteText $ MoveTo p : map LineTo rest
instance IpeWriteText r => IpeWriteText (SimplePolygon () r) where
ipeWriteText pg = case pg^..outerBoundary.to F.toList.Tr.traverse.core of
(p : rest) -> unlines' . map ipeWriteText $ MoveTo p : map LineTo rest ++ [ClosePath]
_ -> Nothing
instance IpeWriteText r => IpeWriteText (PathSegment r) where
ipeWriteText (PolyLineSegment p) = ipeWriteText p
ipeWriteText (PolygonPath p) = ipeWriteText p
ipeWriteText (EllipseSegment m) = ipeWriteText $ Ellipse m
instance IpeWriteText r => IpeWrite (Path r) where
ipeWrite p = (\t -> Element "path" [] [Text t]) <$> ipeWriteText p
instance (IpeWriteText r) => IpeWrite (Group r) where
ipeWrite (Group gs) = case mapMaybe ipeWrite gs of
[] -> Nothing
ns -> (Just $ Element "group" [] ns)
instance ( AllSatisfy IpeAttrName rs
, RecAll (Attr f) rs IpeWriteText
, IpeWrite g
) => IpeWrite (g :+ IA.Attributes f rs) where
ipeWrite (g :+ ats) = ipeWrite g `mAddAtts` ipeWriteAttrs ats
instance IpeWriteText r => IpeWrite (MiniPage r) where
ipeWrite (MiniPage t p w) = (\pt wt ->
Element "text" [ ("pos", pt)
, ("type", "minipage")
, ("width", wt)
] [Text t]
) <$> ipeWriteText p
<*> ipeWriteText w
instance IpeWriteText r => IpeWrite (Image r) where
ipeWrite (Image d (Box a b)) = (\dt p q ->
Element "image" [("rect", p <> " " <> q)] [Text dt]
)
<$> ipeWriteText d
<*> ipeWriteText (a^.core.cwMin)
<*> ipeWriteText (b^.core.cwMax)
instance IpeWriteText () where
ipeWriteText () = Nothing
instance IpeWriteText r => IpeWrite (TextLabel r) where
ipeWrite (Label t p) = (\pt ->
Element "text" [("pos", pt)
,("type", "label")
] [Text t]
) <$> ipeWriteText p
instance (IpeWriteText r) => IpeWrite (IpeObject r) where
ipeWrite (IpeGroup g) = ipeWrite g
ipeWrite (IpeImage i) = ipeWrite i
ipeWrite (IpeTextLabel l) = ipeWrite l
ipeWrite (IpeMiniPage m) = ipeWrite m
ipeWrite (IpeUse s) = ipeWrite s
ipeWrite (IpePath p) = ipeWrite p
ipeWriteRec :: RecAll f rs IpeWrite => Rec f rs -> [Node Text Text]
ipeWriteRec = catMaybes . recordToList
. rmap (\(Compose (Dict x)) -> Const $ ipeWrite x)
. reifyConstraint (Proxy :: Proxy IpeWrite)
deriving instance IpeWriteText LayerName
instance IpeWrite LayerName where
ipeWrite (LayerName n) = Just $ Element "layer" [("name",n)] []
instance IpeWrite View where
ipeWrite (View lrs act) = Just $ Element "view" [ ("layers", ls)
, ("active", _layerName act)
] []
where
ls = T.unwords . map _layerName $ lrs
instance (IpeWriteText r) => IpeWrite (IpePage r) where
ipeWrite (IpePage lrs vs objs) = Just .
Element "page" [] . catMaybes . concat $
[ map ipeWrite lrs
, map ipeWrite vs
, map ipeWrite objs
]
instance IpeWrite IpeStyle where
ipeWrite (IpeStyle _ xml) = Just xml
instance IpeWrite IpePreamble where
ipeWrite (IpePreamble _ latex) = Just $ Element "preamble" [] [Text latex]
instance (IpeWriteText r) => IpeWrite (IpeFile r) where
ipeWrite (IpeFile mp ss pgs) = Just $ Element "ipe" ipeAtts chs
where
ipeAtts = [("version","70005"),("creator", "HGeometry")]
chs = mconcat [ catMaybes [mp >>= ipeWrite]
, mapMaybe ipeWrite ss
, mapMaybe ipeWrite . F.toList $ pgs
]
instance (IpeWriteText r, IpeWrite p) => IpeWrite (PolyLine 2 p r) where
ipeWrite p = ipeWrite path
where
path = fromPolyLine $ p & points.Tr.traverse.extra .~ ()
fromPolyLine :: PolyLine 2 () r -> Path r
fromPolyLine = Path . S2.l1Singleton . PolyLineSegment
instance (IpeWriteText r) => IpeWrite (LineSegment 2 p r) where
ipeWrite (LineSegment' p q) = ipeWrite . fromPolyLine . fromPoints . map (extra .~ ()) $ [p,q]
instance IpeWrite () where
ipeWrite = const Nothing
combine :: [Node Text Text] -> Maybe (Node Text Text)
combine [] = Nothing
combine [n] = Just n
combine ns = Just $ Element "group" [] ns