waterfall-cad-svg 0.6.3.2 → 0.7.0.0
raw patch · 4 files changed
+212/−55 lines, 4 filesdep +deepseqdep ~opencascade-hsdep ~waterfall-cadPVP ok
version bump matches the API change (PVP)
Dependencies added: deepseq
Dependency ranges changed: opencascade-hs, waterfall-cad
API changes (from Hackage documentation)
+ Waterfall.SVG.FromSVG: instance Control.DeepSeq.NFData Waterfall.SVG.FromSVG.SVGError
+ Waterfall.SVG.FromSVG: instance Control.DeepSeq.NFData Waterfall.SVG.FromSVG.SVGErrorKind
+ Waterfall.SVG.FromSVG: instance GHC.Generics.Generic Waterfall.SVG.FromSVG.SVGError
+ Waterfall.SVG.FromSVG: instance GHC.Generics.Generic Waterfall.SVG.FromSVG.SVGErrorKind
+ Waterfall.SVG.ToSVG: LineStyle :: Colour -> Colour -> Double -> LineStyle
+ Waterfall.SVG.ToSVG: [hiddenLineColour] :: LineStyle -> Colour
+ Waterfall.SVG.ToSVG: [hiddenLineOpacity] :: LineStyle -> Double
+ Waterfall.SVG.ToSVG: [visibleLineColour] :: LineStyle -> Colour
+ Waterfall.SVG.ToSVG: data LineStyle
+ Waterfall.SVG.ToSVG: diagramToSvgWithLineStyle :: LineStyle -> Diagram -> Document
+ Waterfall.SVG.ToSVG: instance Control.DeepSeq.NFData Waterfall.SVG.ToSVG.ForceablePathCommand
+ Waterfall.SVG.ToSVG: instance Control.DeepSeq.NFData Waterfall.SVG.ToSVG.LineStyle
+ Waterfall.SVG.ToSVG: instance GHC.Classes.Eq Waterfall.SVG.ToSVG.LineStyle
+ Waterfall.SVG.ToSVG: instance GHC.Classes.Ord Waterfall.SVG.ToSVG.LineStyle
+ Waterfall.SVG.ToSVG: instance GHC.Generics.Generic Waterfall.SVG.ToSVG.LineStyle
+ Waterfall.SVG.ToSVG: instance GHC.Show.Show Waterfall.SVG.ToSVG.LineStyle
+ Waterfall.SVG.ToSVG: lineStyleCss :: LineStyle -> [CssRule]
Files
- CHANGELOG.md +20/−0
- src/Waterfall/SVG/FromSVG.hs +9/−2
- src/Waterfall/SVG/ToSVG.hs +176/−48
- waterfall-cad-svg.cabal +7/−5
CHANGELOG.md view
@@ -8,6 +8,26 @@ ## Unreleased +## 0.7.0.0++### Added++- Added paths for painted regions of a `Diagram`, with class `fill`+- Added `Waterfall.SVG.ToSVG.LineStyle`, `Waterfall.SVG.ToSVG.lineStyleCss` and `Waterfall.SVG.ToSVG.diagramToSvgWithLineStyle`+- Added `Generic` and [`NFData`](https://hackage-content.haskell.org/package/deepseq/docs/Control-DeepSeq.html#t:NFData) instances for the following+ - `Waterfall.SVG.ToSVG.LineStyle`+ - `Waterfall.SVG.FromSVG.SVGError`+ - `Waterfall.SVG.FromSVG.SVGErrorKind`++### Changed++- Edge paths carry only the class `edge`: `visible`/`hidden` and `sharp`/`outline` are now classes on the enclosing groups+- The default hidden-line colour and opacity changed from light blue at full opacity to black at 0.4 opacity++### Fixed++- `path2DToPathCommands` generated extraneous `MoveTo` commands, and incorrectly omitted the `MoveTo` command before the first segment of loops with two edges+ ## 0.6.3.2 ## 0.6.3.1
src/Waterfall/SVG/FromSVG.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE TypeApplications #-}+{-# LANGUAGE DeriveGeneric #-} {-| Load [SVG Data](https://developer.mozilla.org/en-US/docs/Web/SVG) into `Waterfall.Path2D` -}@@ -29,6 +30,8 @@ import Data.Bifunctor (first) import Data.Maybe (catMaybes) import Data.Function ((&))+import GHC.Generics (Generic)+import Control.DeepSeq (NFData) -- | Categories of error that may occur when processing an SVG data SVGErrorKind@@ -38,11 +41,15 @@ | SVGPathError | SVGTransformError | SVGNumberError- deriving (Eq, Ord, Show)+ deriving (Eq, Ord, Show, Generic) +instance NFData SVGErrorKind+ -- | Type representing an error that occured when processing an SVG data SVGError = SVGError SVGErrorKind String- deriving (Eq, Ord, Show)+ deriving (Eq, Ord, Show, Generic)++instance NFData SVGError uncurry6 :: (a -> b -> c -> d -> e -> f -> g) -> (a, b, c, d, e, f) -> g uncurry6 fn (a, b, c, d, e, f) = fn a b c d e f
src/Waterfall/SVG/ToSVG.hs view
@@ -1,9 +1,14 @@ {-# Language OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveGeneric #-} {-| Convert "Waterfall" data into [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG) -} module Waterfall.SVG.ToSVG ( path2DToPathCommands+, LineStyle (..)+, lineStyleCss+, diagramToSvgWithLineStyle , diagramToSvg , writeDiagramSVG ) where@@ -13,7 +18,7 @@ import qualified Graphics.Svg.CssTypes as Svg.Css import Waterfall.Internal.NearZero (nearZero) import Linear (_xy, _x, _y, V2 (..))-import Control.Lens ((^.), (&), (.~))+import Control.Lens ((^.), (&), (.~), (<&>)) import Foreign.Ptr (Ptr) import Control.Monad ((<=<)) import Control.Monad.IO.Class (liftIO)@@ -37,7 +42,32 @@ import OpenCascade.Inheritance (upcast) import Data.Acquire (Acquire) import Codec.Picture.Types (PixelRGBA8 (..))+import Data.Traversable (mapAccumM)+import Data.Maybe (fromMaybe)+import Control.Applicative ((<|>))+import Control.Arrow ((>>>))+import Control.DeepSeq (NFData (rnf))+import GHC.Generics (Generic) +-- SVG.PathCommand doesn't have an NFData instance+-- which is needed for it to be the target of an `unsafeFromAcquire`+newtype ForceablePathCommand = ForceablePathCommand + { getForceablePathCommand :: Svg.PathCommand+ }++instance NFData ForceablePathCommand where+ rnf (ForceablePathCommand (Svg.MoveTo o ps)) = (o `seq` ()) `seq` rnf ps+ rnf (ForceablePathCommand (Svg.LineTo o ps)) = (o `seq` ()) `seq` rnf ps+ rnf (ForceablePathCommand (Svg.HorizontalTo o cs)) = (o `seq` ()) `seq` rnf cs+ rnf (ForceablePathCommand (Svg.VerticalTo o cs)) = (o `seq` ()) `seq` rnf cs+ rnf (ForceablePathCommand (Svg.CurveTo o ps)) = (o `seq` ()) `seq` rnf ps+ rnf (ForceablePathCommand (Svg.SmoothCurveTo o ps)) = (o `seq` ()) `seq` rnf ps+ rnf (ForceablePathCommand (Svg.QuadraticBezier o ps)) = (o `seq` ()) `seq` rnf ps+ rnf (ForceablePathCommand (Svg.SmoothQuadraticBezierCurveTo o ps)) = (o `seq` ()) `seq` rnf ps+ rnf (ForceablePathCommand (Svg.EllipticalArc o as)) = (o `seq` ()) `seq` rnf as+ rnf (ForceablePathCommand Svg.EndPath) = ()++ lineToPathCommand :: Ptr TopoDS.Edge -> IO [Svg.PathCommand] lineToPathCommand edge = do (_s, e) <- Internal.Edges.edgeEndpoints edge@@ -108,42 +138,118 @@ return . pure . Svg.LineTo Svg.OriginAbsolute $ (^. _xy) <$> ps -edgeToPathCommand :: Maybe (V2 Double) -> Ptr TopoDS.Edge -> (Maybe (V2 Double), [Svg.PathCommand])-edgeToPathCommand curPos edge = Internal.Finalizers.unsafeFromAcquire $ do- startPos <- liftIO $ (^. _xy) <$> Internal.Edges.edgeValue edge 0- endPos <- liftIO $ (^. _xy) <$> Internal.Edges.edgeValue edge 1- let hasntMoved = all nearZero . (startPos -) <$> curPos- let addMoveCommand = - case hasntMoved of - Just True -> id- _ -> ((Svg.MoveTo Svg.OriginAbsolute . pure $ startPos) :)- adaptor <- BRepAdaptor.Curve.fromEdge edge- curveType <- liftIO $ BRepAdaptor.Curve.curveType adaptor- thisSegment <-- case curveType of - GeomAbs.CurveType.Line -> liftIO $ lineToPathCommand edge- GeomAbs.CurveType.BezierCurve -> bezierToPathCommand edge adaptor- -- GeomAbs.CurveType.BSplineCurve -> There's some argument for special casing this, but we don't need to- _ -> approximateCurveToPathCommand edge- return (Just endPos, addMoveCommand thisSegment)+edgeToPathCommand :: Maybe (V2 Double) -> Ptr TopoDS.Edge -> Acquire (Maybe (V2 Double), [Svg.PathCommand])+edgeToPathCommand curPos edge = do + startPos <- liftIO $ (^. _xy) <$> Internal.Edges.edgeValue edge 0+ endPos <- liftIO $ (^. _xy) <$> Internal.Edges.edgeValue edge 1+ let hasntMoved = all nearZero . (startPos -) <$> curPos+ let addMoveCommand = + case hasntMoved of + Just True -> id+ _ -> ((Svg.MoveTo Svg.OriginAbsolute . pure $ startPos) :)+ adaptor <- BRepAdaptor.Curve.fromEdge edge+ curveType <- liftIO $ BRepAdaptor.Curve.curveType adaptor+ thisSegment <-+ case curveType of + GeomAbs.CurveType.Line -> liftIO $ lineToPathCommand edge+ GeomAbs.CurveType.BezierCurve -> bezierToPathCommand edge adaptor+ -- GeomAbs.CurveType.BSplineCurve -> There's some argument for special casing this, but we don't need to+ _ -> approximateCurveToPathCommand edge+ return (Just endPos, addMoveCommand thisSegment) +shapeToPathCommands :: Waterfall.Shape -> [Svg.PathCommand]+shapeToPathCommands = + let stripMoveTo = take 1 <> filter (not . isMoveTo) . drop 1+ isMoveTo (Svg.MoveTo _ _) = True+ isMoveTo _ = False+ in concatMap (stripMoveTo. path2DToPathCommands)+ . Waterfall.shapePaths + +colourToPixel :: Waterfall.Colour -> PixelRGBA8+colourToPixel (Waterfall.Colour r g b) =+ let conv = round . (* 255) . min 1 . max 0+ in PixelRGBA8 (conv r) (conv g) (conv b) 255++fillToPath :: (Waterfall.Paint, Waterfall.Shape) -> Svg.Path+fillToPath (paint, shape) =+ let colour = fromMaybe (Waterfall.Colour 1 1 1) (paint ^. Waterfall.paintColour <|> Waterfall.defaultPaint ^. Waterfall.paintColour)+ opacity = fromMaybe (1.0) (paint ^. Waterfall.paintOpacity <|> Waterfall.defaultPaint ^. Waterfall.paintOpacity)+ setOpacity = if opacity == 1.0 + then id + else Svg.drawAttr . Svg.fillOpacity .~ (pure . realToFrac $ opacity)++ in Svg.defaultSvg+ & Svg.pathDefinition .~ shapeToPathCommands shape+ & Svg.drawAttr . Svg.fillColor .~ (pure . Svg.ColorRef . colourToPixel $ colour)+ & setOpacity+ -- | Convert a `Waterfall.Path2D` into a list of `Svg.PathCommands` path2DToPathCommands :: Waterfall.Path2D -> [Svg.PathCommand] path2DToPathCommands (Path2D theRawPath) = case theRawPath of Internal.Path.Common.EmptyRawPath -> [] Internal.Path.Common.SinglePointRawPath _ -> [] Internal.Path.Common.ComplexRawPath wire -> - Internal.Finalizers.unsafeFromAcquireT $- mconcat - . fmap snd- . scanr (flip (edgeToPathCommand . fst)) (Nothing, []) - <$> Internal.Edges.wireEdges wire+ fmap getForceablePathCommand + . Internal.Finalizers.unsafeFromAcquire $+ fmap (+ fmap ForceablePathCommand + . concat + . snd ) + . mapAccumM edgeToPathCommand Nothing + =<< (Internal.Edges.wireEdges + =<< Internal.Finalizers.acquireForeignPtr wire) ++-- | How lines should appear+-- +-- These values are used to generate inline CSS+data LineStyle = LineStyle+ { visibleLineColour :: Waterfall.Colour+ , hiddenLineColour :: Waterfall.Colour+ , hiddenLineOpacity :: Double+ } deriving (Eq, Ord, Show, Generic)++instance NFData LineStyle++++defaultLineStyle :: LineStyle+defaultLineStyle = LineStyle+ { visibleLineColour = Waterfall.Colour 0 0 0+ , hiddenLineColour = Waterfall.Colour 0 0 0+ , hiddenLineOpacity = 0.4+ }++-- | The inline CSS used to style an SVG diagram+lineStyleCss :: LineStyle -> [Svg.Css.CssRule]+lineStyleCss (LineStyle {..}) = + [ Svg.Css.CssRule + [[Svg.Css.AllOf [Svg.Css.OfClass "edge"]]]+ [ Svg.Css.CssDeclaration "fill" [[Svg.Css.CssIdent "None"]]+ , Svg.Css.CssDeclaration "fill-opacity" [[Svg.Css.CssNumber (Svg.Num 0)]]+ , Svg.Css.CssDeclaration "stroke-linecap" [[Svg.Css.CssIdent "round"]]+ ]+ , Svg.Css.CssRule+ [[Svg.Css.AllOf [Svg.Css.OfClass "visible"], Svg.Css.AllOf[Svg.Css.OfClass "edge"]]]+ [Svg.Css.CssDeclaration "stroke" [[Svg.Css.CssColor . colourToPixel $ visibleLineColour]]]+ , Svg.Css.CssRule+ [[Svg.Css.AllOf [Svg.Css.OfClass "hidden"], Svg.Css.AllOf [Svg.Css.OfClass "edge"]]]+ [ Svg.Css.CssDeclaration "stroke" [[Svg.Css.CssColor . colourToPixel $ hiddenLineColour]]+ , Svg.Css.CssDeclaration "stroke-opacity" [[Svg.Css.CssNumber . Svg.Num $ hiddenLineOpacity]]+ ]+ , Svg.Css.CssRule+ [[Svg.Css.AllOf [Svg.Css.OfClass "fill"]]]+ [ Svg.Css.CssDeclaration "fill-rule" [[Svg.Css.CssIdent "evenodd"]]]+ ]+ -- | Convert a `Waterfall.Diagram` into an SVG document -- --- The diagram paths have the classes "edge", "visible"\/"hidden" and "sharp"\/"outline"-diagramToSvg :: Waterfall.Diagram -> Svg.Document-diagramToSvg diagram = +-- The diagram paths have the class "edge"+-- and are grouped within elements with class "sharp"\/"outline" and "visible"\/"hidden"+-- +-- The diagram fills have the class "fill"+diagramToSvgWithLineStyle :: LineStyle -> Waterfall.Diagram -> Svg.Document+diagramToSvgWithLineStyle lineStyle diagram = case Waterfall.diagramBoundingBox diagram of Nothing -> Svg.Document Nothing Nothing Nothing [] mempty mempty mempty mempty Just (lo, hi) -> @@ -153,30 +259,52 @@ paths lt visibility = path2DToPathCommands =<< Waterfall.diagramLines lt visibility d'- styles = - [ Svg.Css.CssRule - [[Svg.Css.AllOf [Svg.Css.OfClass "edge"]]]- [ Svg.Css.CssDeclaration "fill" [[Svg.Css.CssIdent "None"]]- , Svg.Css.CssDeclaration "fill-opacity" [[Svg.Css.CssNumber (Svg.Num 0)]]- ]- , Svg.Css.CssRule- [[Svg.Css.AllOf [Svg.Css.OfClass "edge", Svg.Css.OfClass "visible"]]]- [Svg.Css.CssDeclaration "stroke" [[Svg.Css.CssColor $ PixelRGBA8 0 0 0 255]]]- , Svg.Css.CssRule- [[Svg.Css.AllOf [Svg.Css.OfClass "edge", Svg.Css.OfClass "hidden"]]]- [Svg.Css.CssDeclaration "stroke" [[Svg.Css.CssColor $ PixelRGBA8 200 200 255 255]]]- ]+ styles = lineStyleCss lineStyle document e = Svg.Document Nothing w h [e] mempty mempty styles mempty- drawAttrs classes = mempty - & Svg.attrClass .~ classes- pathOf lt visibility classes = Svg.PathTree $ Svg.Path (drawAttrs classes) (paths lt visibility)- group children = Svg.GroupTree $ Svg.Group mempty children Nothing Svg.defaultSvg- in document . group $- [ pathOf lineType visibility ["edge", ltClass, vClass]- | (visibility, vClass) <- [(Waterfall.Hidden, "hidden"), (Waterfall.Visible, "visible")]- , (lineType, ltClass) <- [(Waterfall.SharpLine, "sharp"), (Waterfall.OutLine, "outline")]+ pathOf lt visibility = + Svg.defaultSvg + & Svg.pathDefinition .~ paths lt visibility+ & Svg.drawAttr . Svg.attrClass .~ ["edge"]+ & Svg.PathTree+ + fills = + Svg.defaultSvg + & Svg.groupChildren .~ + (Waterfall.getDiagramFills d' + <&> (fillToPath+ >>> Svg.drawAttr . Svg.attrClass .~ ["fill"]+ >>> Svg.PathTree+ )+ )+ & Svg.GroupTree+ + group children+ = Svg.defaultSvg+ & Svg.groupChildren .~ children+ & Svg.GroupTree + in document . group . (fills :) $+ [ Svg.defaultSvg + & Svg.groupChildren .~+ [ Svg.defaultSvg + & Svg.groupChildren .~ [ pathOf lineType visibility ]+ & Svg.drawAttr . Svg.attrClass .~ [ltClass]+ & Svg.GroupTree+ | (lineType, ltClass) <- [(Waterfall.SharpLine, "sharp"), (Waterfall.OutLine, "outline")]+ ] + & Svg.drawAttr . Svg.attrClass .~ [vClass]+ & Svg.GroupTree+ | (visibility, vClass) <- [(Waterfall.Hidden, "hidden"), (Waterfall.Visible, "visible")] ] +-- | Convert a `Waterfall.Diagram` into an SVG document+-- +-- The diagram paths have the class "edge"+-- and are grouped within elements with class "sharp"\/"outline" and "visible"\/"hidden"+-- +-- The diagram fills have the class "fill"+diagramToSvg :: Waterfall.Diagram -> Svg.Document+diagramToSvg = diagramToSvgWithLineStyle defaultLineStyle+ -- | Write a `Waterfall.Diagram`, to an SVG file at the specified location writeDiagramSVG :: FilePath -> Waterfall.Diagram -> IO () writeDiagramSVG path = Svg.saveXmlFile path . diagramToSvg
waterfall-cad-svg.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: waterfall-cad-svg-version: 0.6.3.2+version: 0.7.0.0 synopsis: Declarative CAD/Solid Modeling Library, SVG Support description: Please see the README on GitHub at <https://github.com/joe-warren/opencascade-hs#readme> category: Graphics@@ -43,13 +43,14 @@ JuicyPixels ==3.* , attoparsec >=0.14 && <1 , base >=4.7 && <5+ , deepseq >=1.4 && <2 , lens ==5.* , linear >=1.0 && <2- , opencascade-hs >=0.6.3.2 && <0.7+ , opencascade-hs >=0.7.0.0 && <0.8 , resourcet >=1.2 && <1.4 , svg-tree >=0.6 && <1 , text >=1.2 && <3- , waterfall-cad >=0.6.3.2 && <0.7+ , waterfall-cad >=0.7.0.0 && <0.8 default-language: Haskell2010 test-suite tests@@ -64,14 +65,15 @@ JuicyPixels ==3.* , attoparsec >=0.14 && <1 , base >=4.7 && <5+ , deepseq >=1.4 && <2 , lens ==5.* , linear >=1.0 && <2- , opencascade-hs >=0.6.3.2 && <0.7+ , opencascade-hs >=0.7.0.0 && <0.8 , resourcet >=1.2 && <1.4 , svg-tree >=0.6 && <1 , tasty , tasty-hunit , text >=1.2 && <3- , waterfall-cad >=0.6.3.2 && <0.7+ , waterfall-cad >=0.7.0.0 && <0.8 , waterfall-cad-svg default-language: Haskell2010