wumpus-core 0.23.0 → 0.30.0
raw patch · 40 files changed
+3129/−3270 lines, 40 filesdep −monadLibdep −wl-pprintdep −xmldep ~algebradep ~containersdep ~vector-spacePVP ok
version bump matches the API change (PVP)
Dependencies removed: monadLib, wl-pprint, xml
Dependency ranges changed: algebra, containers, vector-space
API changes (from Hackage documentation)
- Wumpus.Core.BoundingBox: boundaryBottomLeft :: BoundingBox u -> Point2 u
- Wumpus.Core.BoundingBox: boundaryBottomRight :: BoundingBox u -> Point2 u
- Wumpus.Core.BoundingBox: boundaryTopLeft :: BoundingBox u -> Point2 u
- Wumpus.Core.BoundingBox: boundaryTopRight :: BoundingBox u -> Point2 u
- Wumpus.Core.BoundingBox: corners :: BoundingBox u -> (Point2 u, Point2 u, Point2 u, Point2 u)
- Wumpus.Core.BoundingBox: instance (Pretty u) => Pretty (BoundingBox u)
- Wumpus.Core.BoundingBox: instance Pointwise (BoundingBox u)
- Wumpus.Core.BoundingBox: leftPlane :: BoundingBox u -> u
- Wumpus.Core.BoundingBox: lowerPlane :: BoundingBox u -> u
- Wumpus.Core.BoundingBox: rightPlane :: BoundingBox u -> u
- Wumpus.Core.BoundingBox: union :: (Ord u) => BoundingBox u -> BoundingBox u -> BoundingBox u
- Wumpus.Core.BoundingBox: upperPlane :: BoundingBox u -> u
- Wumpus.Core.BoundingBox: within :: (Ord u) => Point2 u -> BoundingBox u -> Bool
- Wumpus.Core.Colour: Gray :: a -> Gray a
- Wumpus.Core.Colour: HSB3 :: !a -> !a -> !a -> HSB3 a
- Wumpus.Core.Colour: RGB3 :: !a -> !a -> !a -> RGB3 a
- Wumpus.Core.Colour: data HSB3 a
- Wumpus.Core.Colour: data RGB3 a
- Wumpus.Core.Colour: gray2hsb :: DGray -> DHSB
- Wumpus.Core.Colour: gray2rgb :: DGray -> DRGB
- Wumpus.Core.Colour: hsb2gray :: DHSB -> DGray
- Wumpus.Core.Colour: hsb2rgb :: DHSB -> DRGB
- Wumpus.Core.Colour: iGray :: (Fractional a, Ord a) => Int -> Gray a
- Wumpus.Core.Colour: iHSB3 :: (Fractional a, Ord a) => Int -> Int -> Int -> HSB3 a
- Wumpus.Core.Colour: iRGB3 :: (Fractional a, Ord a) => Int -> Int -> Int -> RGB3 a
- Wumpus.Core.Colour: instance (Eq a) => Eq (Gray a)
- Wumpus.Core.Colour: instance (Eq a) => Eq (HSB3 a)
- Wumpus.Core.Colour: instance (Eq a) => Eq (RGB3 a)
- Wumpus.Core.Colour: instance (Fractional a) => Fractional (Gray a)
- Wumpus.Core.Colour: instance (Fractional a) => Fractional (HSB3 a)
- Wumpus.Core.Colour: instance (Fractional a) => Fractional (RGB3 a)
- Wumpus.Core.Colour: instance (Num a) => AdditiveGroup (Gray a)
- Wumpus.Core.Colour: instance (Num a) => AdditiveGroup (HSB3 a)
- Wumpus.Core.Colour: instance (Num a) => AdditiveGroup (RGB3 a)
- Wumpus.Core.Colour: instance (Num a) => Num (Gray a)
- Wumpus.Core.Colour: instance (Num a) => Num (HSB3 a)
- Wumpus.Core.Colour: instance (Num a) => Num (RGB3 a)
- Wumpus.Core.Colour: instance (Num a) => VectorSpace (Gray a)
- Wumpus.Core.Colour: instance (Num a) => VectorSpace (HSB3 a)
- Wumpus.Core.Colour: instance (Num a) => VectorSpace (RGB3 a)
- Wumpus.Core.Colour: instance (Ord a) => Ord (Gray a)
- Wumpus.Core.Colour: instance (Show a) => Show (Gray a)
- Wumpus.Core.Colour: instance (Show a) => Show (HSB3 a)
- Wumpus.Core.Colour: instance (Show a) => Show (RGB3 a)
- Wumpus.Core.Colour: newtype Gray a
- Wumpus.Core.Colour: rgb2gray :: DRGB -> DGray
- Wumpus.Core.Colour: rgb2hsb :: DRGB -> DHSB
- Wumpus.Core.Colour: type DGray = Gray Double
- Wumpus.Core.Colour: type DHSB = HSB3 Double
- Wumpus.Core.Colour: type DRGB = RGB3 Double
- Wumpus.Core.Geometry: Frame2 :: (Vec2 u) -> (Vec2 u) -> (Point2 u) -> Frame2 u
- Wumpus.Core.Geometry: class Pointwise sh where { type family Pt sh :: *; }
- Wumpus.Core.Geometry: data Frame2 u
- Wumpus.Core.Geometry: displaceOrigin :: (Num u) => Vec2 u -> Frame2 u -> Frame2 u
- Wumpus.Core.Geometry: frame2Matrix :: (Num u) => Frame2 u -> Matrix3'3 u
- Wumpus.Core.Geometry: frameProduct :: (Num u, InnerSpace (Vec2 u)) => Frame2 u -> Frame2 u -> Frame2 u
- Wumpus.Core.Geometry: instance (Eq u) => Eq (Frame2 u)
- Wumpus.Core.Geometry: instance (Num u) => Monoid (Vec2 u)
- Wumpus.Core.Geometry: instance (Num u, InnerSpace (Vec2 u)) => Monoid (Frame2 u)
- Wumpus.Core.Geometry: instance (PSUnit u) => Pretty (Matrix3'3 u)
- Wumpus.Core.Geometry: instance (Pointwise a) => Pointwise [a]
- Wumpus.Core.Geometry: instance (Pretty u) => Pretty (Frame2 u)
- Wumpus.Core.Geometry: instance (Pretty u) => Pretty (Point2 u)
- Wumpus.Core.Geometry: instance (Pretty u) => Pretty (Vec2 u)
- Wumpus.Core.Geometry: instance (Scalar u ~ u, Num u, InnerSpace u) => InnerSpace (Vec2 u)
- Wumpus.Core.Geometry: instance (Show u) => Show (Frame2 u)
- Wumpus.Core.Geometry: instance Pointwise (Point2 u)
- Wumpus.Core.Geometry: instance Pointwise (Vec2 u)
- Wumpus.Core.Geometry: instance Pointwise (a -> a)
- Wumpus.Core.Geometry: instance Pretty Radian
- Wumpus.Core.Geometry: langle :: (Floating u, Real u) => Point2 u -> Point2 u -> Radian
- Wumpus.Core.Geometry: matrix2Frame :: Matrix3'3 u -> Frame2 u
- Wumpus.Core.Geometry: ortho :: (Num u) => Point2 u -> Frame2 u
- Wumpus.Core.Geometry: pointInFrame :: (Num u) => Point2 u -> Frame2 u -> Point2 u
- Wumpus.Core.Geometry: pointwise :: (Pointwise sh) => (Pt sh -> Pt sh) -> sh -> sh
- Wumpus.Core.Geometry: req :: Radian -> Radian -> Bool
- Wumpus.Core.Geometry: standardFrame :: (Num u) => Frame2 u -> Bool
- Wumpus.Core.Geometry: type DFrame2 = Frame2 Double
- Wumpus.Core.GraphicsState: CTM :: !u -> !u -> !u -> !u -> !u -> !u -> CTM u
- Wumpus.Core.GraphicsState: class PSColour a
- Wumpus.Core.GraphicsState: class ToCTM a
- Wumpus.Core.GraphicsState: data CTM u
- Wumpus.Core.GraphicsState: instance (Eq u) => Eq (CTM u)
- Wumpus.Core.GraphicsState: instance (Show u) => Show (CTM u)
- Wumpus.Core.GraphicsState: instance PSColour (Gray Double)
- Wumpus.Core.GraphicsState: instance PSColour (HSB3 Double)
- Wumpus.Core.GraphicsState: instance PSColour (RGB3 Double)
- Wumpus.Core.GraphicsState: instance ToCTM (Frame2 a)
- Wumpus.Core.GraphicsState: instance ToCTM (Matrix3'3 a)
- Wumpus.Core.GraphicsState: psColour :: (PSColour a) => a -> RGB3 Double
- Wumpus.Core.GraphicsState: toCTM :: (ToCTM a, u ~ (DUnit a)) => a -> CTM u
- Wumpus.Core.GraphicsState: type PSRgb = RGB3 Double
- Wumpus.Core.Picture: blankPicture :: (Num u) => BoundingBox u -> Picture u
- Wumpus.Core.Picture: frameMulti :: (Real u, Floating u, FromPtSize u) => [Primitive u] -> Picture u
- Wumpus.Core.Picture: frameWithin :: (Real u, Floating u, FromPtSize u) => Primitive u -> BoundingBox u -> Picture u
- Wumpus.Core.Picture: instance Ellipse (Gray Double)
- Wumpus.Core.Picture: instance Ellipse (Gray Double, DrawEllipse)
- Wumpus.Core.Picture: instance Ellipse (Gray Double, StrokeAttr)
- Wumpus.Core.Picture: instance Ellipse (Gray Double, [StrokeAttr])
- Wumpus.Core.Picture: instance Ellipse (HSB3 Double)
- Wumpus.Core.Picture: instance Ellipse (HSB3 Double, DrawEllipse)
- Wumpus.Core.Picture: instance Ellipse (HSB3 Double, StrokeAttr)
- Wumpus.Core.Picture: instance Ellipse (HSB3 Double, [StrokeAttr])
- Wumpus.Core.Picture: instance Ellipse (RGB3 Double)
- Wumpus.Core.Picture: instance Ellipse (RGB3 Double, DrawEllipse)
- Wumpus.Core.Picture: instance Ellipse (RGB3 Double, StrokeAttr)
- Wumpus.Core.Picture: instance Ellipse (RGB3 Double, [StrokeAttr])
- Wumpus.Core.Picture: instance Ellipse DrawEllipse
- Wumpus.Core.Picture: instance Fill (Gray Double)
- Wumpus.Core.Picture: instance Fill (HSB3 Double)
- Wumpus.Core.Picture: instance Fill (RGB3 Double)
- Wumpus.Core.Picture: instance Stroke (Gray Double)
- Wumpus.Core.Picture: instance Stroke (Gray Double, StrokeAttr)
- Wumpus.Core.Picture: instance Stroke (Gray Double, [StrokeAttr])
- Wumpus.Core.Picture: instance Stroke (HSB3 Double)
- Wumpus.Core.Picture: instance Stroke (HSB3 Double, StrokeAttr)
- Wumpus.Core.Picture: instance Stroke (HSB3 Double, [StrokeAttr])
- Wumpus.Core.Picture: instance Stroke (RGB3 Double)
- Wumpus.Core.Picture: instance Stroke (RGB3 Double, StrokeAttr)
- Wumpus.Core.Picture: instance Stroke (RGB3 Double, [StrokeAttr])
- Wumpus.Core.Picture: instance TextLabel (Gray Double)
- Wumpus.Core.Picture: instance TextLabel (Gray Double, FontAttr)
- Wumpus.Core.Picture: instance TextLabel (HSB3 Double)
- Wumpus.Core.Picture: instance TextLabel (HSB3 Double, FontAttr)
- Wumpus.Core.Picture: instance TextLabel (RGB3 Double)
- Wumpus.Core.Picture: instance TextLabel (RGB3 Double, FontAttr)
- Wumpus.Core.Picture: wumpus_default_font :: FontAttr
- Wumpus.Core.WumpusTypes: data DrawEllipse
- Wumpus.Core.WumpusTypes: data DrawPath
- Wumpus.Core.WumpusTypes: data Label u
- Wumpus.Core.WumpusTypes: data Path u
- Wumpus.Core.WumpusTypes: data PathSegment u
- Wumpus.Core.WumpusTypes: type DLabel = Label Double
- Wumpus.Core.WumpusTypes: type DPath = Path Double
- Wumpus.Core.WumpusTypes: type DPathSegment = PathSegment Double
- Wumpus.Core.WumpusTypes: type EllipseProps = (PSRgb, DrawEllipse)
- Wumpus.Core.WumpusTypes: type LabelProps = (PSRgb, FontAttr)
- Wumpus.Core.WumpusTypes: type PathProps = (PSRgb, DrawPath)
+ Wumpus.Core.BoundingBox: boundaryCorners :: BoundingBox u -> (Point2 u, Point2 u, Point2 u, Point2 u)
+ Wumpus.Core.BoundingBox: boundaryUnion :: (Ord u) => BoundingBox u -> BoundingBox u -> BoundingBox u
+ Wumpus.Core.BoundingBox: destBoundingBox :: BoundingBox u -> (u, u, u, u)
+ Wumpus.Core.BoundingBox: instance (Num u, Ord u) => Transform (BoundingBox u)
+ Wumpus.Core.BoundingBox: instance (Num u, Ord u) => Translate (BoundingBox u)
+ Wumpus.Core.BoundingBox: instance (PSUnit u) => Format (BoundingBox u)
+ Wumpus.Core.BoundingBox: instance (Real u, Floating u) => Rotate (BoundingBox u)
+ Wumpus.Core.BoundingBox: instance (Real u, Floating u) => RotateAbout (BoundingBox u)
+ Wumpus.Core.BoundingBox: withinBoundary :: (Ord u) => Point2 u -> BoundingBox u -> Bool
+ Wumpus.Core.Colour: RGBi :: !Word8 -> !Word8 -> !Word8 -> RGBi
+ Wumpus.Core.Colour: data RGBi
+ Wumpus.Core.Colour: instance Eq RGBi
+ Wumpus.Core.Colour: instance Format RGBi
+ Wumpus.Core.Colour: instance Ord RGBi
+ Wumpus.Core.Colour: instance Show RGBi
+ Wumpus.Core.Geometry: instance (PSUnit u) => Format (Matrix3'3 u)
+ Wumpus.Core.Geometry: instance (PSUnit u) => Format (Point2 u)
+ Wumpus.Core.Geometry: instance (PSUnit u) => Format (Vec2 u)
+ Wumpus.Core.Geometry: instance (u ~ Scalar u, Num u, InnerSpace u) => InnerSpace (Vec2 u)
+ Wumpus.Core.Geometry: instance Format Radian
+ Wumpus.Core.Geometry: lineDirection :: (Floating u, Real u) => Point2 u -> Point2 u -> Radian
+ Wumpus.Core.Geometry: point_x :: Point2 u -> !u
+ Wumpus.Core.Geometry: point_y :: Point2 u -> !u
+ Wumpus.Core.Geometry: vector_x :: Vec2 u -> !u
+ Wumpus.Core.Geometry: vector_y :: Vec2 u -> !u
+ Wumpus.Core.GraphicsState: GraphicsState :: RGBi -> Int -> FontFace -> Double -> Double -> LineCap -> LineJoin -> DashPattern -> GraphicsState
+ Wumpus.Core.GraphicsState: data GraphicsState
+ Wumpus.Core.GraphicsState: gs_dash_pattern :: GraphicsState -> DashPattern
+ Wumpus.Core.GraphicsState: gs_draw_colour :: GraphicsState -> RGBi
+ Wumpus.Core.GraphicsState: gs_font_face :: GraphicsState -> FontFace
+ Wumpus.Core.GraphicsState: gs_font_size :: GraphicsState -> Int
+ Wumpus.Core.GraphicsState: gs_line_cap :: GraphicsState -> LineCap
+ Wumpus.Core.GraphicsState: gs_line_join :: GraphicsState -> LineJoin
+ Wumpus.Core.GraphicsState: gs_line_width :: GraphicsState -> Double
+ Wumpus.Core.GraphicsState: gs_miter_limit :: GraphicsState -> Double
+ Wumpus.Core.GraphicsState: instance Eq GraphicsState
+ Wumpus.Core.GraphicsState: instance Show GraphicsState
+ Wumpus.Core.GraphicsState: zeroGS :: GraphicsState
+ Wumpus.Core.OutputPostScript: instance Applicative PsMonad
+ Wumpus.Core.OutputPostScript: instance Functor PsMonad
+ Wumpus.Core.OutputPostScript: instance Monad PsMonad
+ Wumpus.Core.OutputSVG: instance Applicative SvgMonad
+ Wumpus.Core.OutputSVG: instance Functor SvgMonad
+ Wumpus.Core.OutputSVG: instance Monad SvgMonad
+ Wumpus.Core.Picture: instance Ellipse (RGBi, StrokeAttr)
+ Wumpus.Core.Picture: instance Ellipse (RGBi, XLink)
+ Wumpus.Core.Picture: instance Ellipse (RGBi, [StrokeAttr])
+ Wumpus.Core.Picture: instance Ellipse (RGBi, [StrokeAttr], XLink)
+ Wumpus.Core.Picture: instance Ellipse (StrokeAttr, XLink)
+ Wumpus.Core.Picture: instance Ellipse ([StrokeAttr], XLink)
+ Wumpus.Core.Picture: instance Ellipse RGBi
+ Wumpus.Core.Picture: instance Ellipse XLink
+ Wumpus.Core.Picture: instance Fill (RGBi, XLink)
+ Wumpus.Core.Picture: instance Fill RGBi
+ Wumpus.Core.Picture: instance Fill XLink
+ Wumpus.Core.Picture: instance Stroke (RGBi, StrokeAttr)
+ Wumpus.Core.Picture: instance Stroke (RGBi, StrokeAttr, XLink)
+ Wumpus.Core.Picture: instance Stroke (RGBi, XLink)
+ Wumpus.Core.Picture: instance Stroke (RGBi, [StrokeAttr])
+ Wumpus.Core.Picture: instance Stroke (RGBi, [StrokeAttr], XLink)
+ Wumpus.Core.Picture: instance Stroke (StrokeAttr, XLink)
+ Wumpus.Core.Picture: instance Stroke ([StrokeAttr], XLink)
+ Wumpus.Core.Picture: instance Stroke RGBi
+ Wumpus.Core.Picture: instance Stroke XLink
+ Wumpus.Core.Picture: instance TextLabel (FontAttr, XLink)
+ Wumpus.Core.Picture: instance TextLabel (RGBi, FontAttr)
+ Wumpus.Core.Picture: instance TextLabel (RGBi, FontAttr, XLink)
+ Wumpus.Core.Picture: instance TextLabel (RGBi, XLink)
+ Wumpus.Core.Picture: instance TextLabel RGBi
+ Wumpus.Core.Picture: instance TextLabel XLink
+ Wumpus.Core.Picture: printPicture :: (Num u, PSUnit u) => Picture u -> IO ()
+ Wumpus.Core.Picture: xlinkhref :: String -> XLink
+ Wumpus.Core.WumpusTypes: data EllipseProps
+ Wumpus.Core.WumpusTypes: data GSUpdate
+ Wumpus.Core.WumpusTypes: data LabelProps
+ Wumpus.Core.WumpusTypes: data PathProps
+ Wumpus.Core.WumpusTypes: data PrimLabel u
+ Wumpus.Core.WumpusTypes: data PrimPath u
+ Wumpus.Core.WumpusTypes: data PrimPathSegment u
+ Wumpus.Core.WumpusTypes: type DPrimLabel = PrimLabel Double
+ Wumpus.Core.WumpusTypes: type DPrimPath = PrimPath Double
+ Wumpus.Core.WumpusTypes: type DPrimPathSegment = PrimPathSegment Double
- Wumpus.Core.BoundingBox: boundary :: (Boundary a, (DUnit a) ~ u) => a -> BoundingBox u
+ Wumpus.Core.BoundingBox: boundary :: (Boundary t, u ~ (DUnit t)) => t -> BoundingBox u
- Wumpus.Core.BoundingBox: class Boundary a
+ Wumpus.Core.BoundingBox: class Boundary t
- Wumpus.Core.Colour: black :: DRGB
+ Wumpus.Core.Colour: black :: RGBi
- Wumpus.Core.Colour: blue :: DRGB
+ Wumpus.Core.Colour: blue :: RGBi
- Wumpus.Core.Colour: green :: DRGB
+ Wumpus.Core.Colour: green :: RGBi
- Wumpus.Core.Colour: red :: DRGB
+ Wumpus.Core.Colour: red :: RGBi
- Wumpus.Core.Colour: white :: DRGB
+ Wumpus.Core.Colour: white :: RGBi
- Wumpus.Core.Picture: clip :: (Num u, Ord u) => Path u -> Picture u -> Picture u
+ Wumpus.Core.Picture: clip :: (Num u, Ord u) => PrimPath u -> Picture u -> Picture u
- Wumpus.Core.Picture: cstroke :: (Stroke t, Num u) => t -> Path u -> Primitive u
+ Wumpus.Core.Picture: cstroke :: (Stroke t, Num u) => t -> PrimPath u -> Primitive u
- Wumpus.Core.Picture: curveTo :: Point2 u -> Point2 u -> Point2 u -> PathSegment u
+ Wumpus.Core.Picture: curveTo :: Point2 u -> Point2 u -> Point2 u -> PrimPathSegment u
- Wumpus.Core.Picture: curvedPath :: [Point2 u] -> Path u
+ Wumpus.Core.Picture: curvedPath :: [Point2 u] -> PrimPath u
- Wumpus.Core.Picture: fill :: (Fill t, Num u) => t -> Path u -> Primitive u
+ Wumpus.Core.Picture: fill :: (Fill t, Num u) => t -> PrimPath u -> Primitive u
- Wumpus.Core.Picture: frame :: (Real u, Floating u, FromPtSize u) => Primitive u -> Picture u
+ Wumpus.Core.Picture: frame :: (Real u, Floating u, FromPtSize u) => [Primitive u] -> Picture u
- Wumpus.Core.Picture: illustrateBounds :: (Real u, Floating u, FromPtSize u) => DRGB -> Picture u -> Picture u
+ Wumpus.Core.Picture: illustrateBounds :: (Real u, Floating u, FromPtSize u) => RGBi -> Picture u -> Picture u
- Wumpus.Core.Picture: illustrateBoundsPrim :: (Real u, Floating u, FromPtSize u) => DRGB -> Primitive u -> Picture u
+ Wumpus.Core.Picture: illustrateBoundsPrim :: (Real u, Floating u, FromPtSize u) => RGBi -> Primitive u -> Picture u
- Wumpus.Core.Picture: illustrateControlPoints :: (Real u, Floating u, FromPtSize u) => DRGB -> Primitive u -> Picture u
+ Wumpus.Core.Picture: illustrateControlPoints :: (Real u, Floating u, FromPtSize u) => RGBi -> Primitive u -> Picture u
- Wumpus.Core.Picture: lineTo :: Point2 u -> PathSegment u
+ Wumpus.Core.Picture: lineTo :: Point2 u -> PrimPathSegment u
- Wumpus.Core.Picture: ostroke :: (Stroke t, Num u) => t -> Path u -> Primitive u
+ Wumpus.Core.Picture: ostroke :: (Stroke t, Num u) => t -> PrimPath u -> Primitive u
- Wumpus.Core.Picture: path :: Point2 u -> [PathSegment u] -> Path u
+ Wumpus.Core.Picture: path :: Point2 u -> [PrimPathSegment u] -> PrimPath u
- Wumpus.Core.Picture: picMoveBy :: (Num u) => Picture u -> Vec2 u -> Picture u
+ Wumpus.Core.Picture: picMoveBy :: (Num u, Ord u) => Picture u -> Vec2 u -> Picture u
- Wumpus.Core.Picture: vertexPath :: [Point2 u] -> Path u
+ Wumpus.Core.Picture: vertexPath :: [Point2 u] -> PrimPath u
- Wumpus.Core.Picture: zcstroke :: (Num u) => Path u -> Primitive u
+ Wumpus.Core.Picture: zcstroke :: (Num u) => PrimPath u -> Primitive u
- Wumpus.Core.Picture: zfill :: (Num u) => Path u -> Primitive u
+ Wumpus.Core.Picture: zfill :: (Num u) => PrimPath u -> Primitive u
- Wumpus.Core.Picture: zostroke :: (Num u) => Path u -> Primitive u
+ Wumpus.Core.Picture: zostroke :: (Num u) => PrimPath u -> Primitive u
Files
- LICENSE +1/−1
- demo/AffineTestBase.hs +24/−80
- demo/FontMetrics.hs +10/−11
- demo/LabelPic.hs +12/−13
- demo/MultiPic.hs +2/−2
- demo/Rotated.hs +8/−8
- demo/Scaled.hs +8/−8
- demo/Translated.hs +8/−8
- demo/ZOrderPic.hs +35/−0
- doc-src/Guide.lhs +25/−26
- doc-src/Makefile +2/−0
- doc-src/WorldFrame.hs +4/−4
- doc/Guide.pdf binary
- src/Wumpus/Core.hs +2/−2
- src/Wumpus/Core/AffineTrans.hs +29/−9
- src/Wumpus/Core/BoundingBox.hs +51/−89
- src/Wumpus/Core/Colour.hs +43/−245
- src/Wumpus/Core/FontSize.hs +1/−1
- src/Wumpus/Core/FormatCombinators.hs +384/−0
- src/Wumpus/Core/Geometry.hs +80/−215
- src/Wumpus/Core/GraphicsState.hs +38/−110
- src/Wumpus/Core/OneList.hs +8/−10
- src/Wumpus/Core/OutputPostScript.hs +379/−237
- src/Wumpus/Core/OutputSVG.hs +258/−232
- src/Wumpus/Core/PageTranslation.hs +65/−0
- src/Wumpus/Core/Picture.hs +254/−273
- src/Wumpus/Core/PictureInternal.hs +416/−396
- src/Wumpus/Core/PostScript.hs +0/−544
- src/Wumpus/Core/PostScriptDoc.hs +350/−0
- src/Wumpus/Core/PtSize.hs +4/−2
- src/Wumpus/Core/SVG.hs +0/−436
- src/Wumpus/Core/SVGDoc.hs +382/−0
- src/Wumpus/Core/TextEncoder.hs +3/−3
- src/Wumpus/Core/TextEncodingInternal.hs +0/−101
- src/Wumpus/Core/TextInternal.hs +100/−0
- src/Wumpus/Core/TextLatin1.hs +1/−1
- src/Wumpus/Core/Utils.hs +65/−165
- src/Wumpus/Core/VersionNumber.hs +5/−5
- src/Wumpus/Core/WumpusTypes.hs +12/−11
- wumpus-core.cabal +60/−22
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2008 Stephen Peter Tetley+Copyright (c) 2009-2010 Stephen Peter Tetley All rights reserved.
demo/AffineTestBase.hs view
@@ -25,8 +25,8 @@ import System.Directory -light_blue :: DRGB -light_blue = iRGB3 176 224 231 +light_blue :: RGBi +light_blue = RGBi 176 224 231 runAlgs :: [AffineTrafoAlg] -> [ControlPointAlg] -> IO () @@ -39,7 +39,7 @@ { ata_console_msg :: String , ata_eps_file :: FilePath , ata_svg_file :: FilePath - , ata_prim_constructor :: DRGB -> DPrimitive + , ata_prim_constructor :: RGBi -> DPrimitive , ata_pic_transformer :: DPicture -> DPicture , ata_prim_transformer :: DPrimitive -> DPrimitive } @@ -56,7 +56,7 @@ (ata_prim_transformer ata) -buildPictureATA :: (DRGB -> DPrimitive) +buildPictureATA :: (RGBi -> DPrimitive) -> (DPicture -> DPicture) -> (DPrimitive -> DPrimitive) -> DPicture @@ -64,10 +64,10 @@ picture1 `picBeside` picture2 `picBeside` picture3 where picture1 :: DPicture - picture1 = illustrateBounds light_blue $ frame $ (mk black) + picture1 = illustrateBounds light_blue $ frame [mk black] picture2 :: DPicture - picture2 = illustrateBounds light_blue $ picF $ frame $ (mk blue) + picture2 = illustrateBounds light_blue $ picF $ frame [mk blue] picture3 :: DPicture picture3 = illustrateBoundsPrim light_blue $ prim @@ -85,7 +85,7 @@ { cpa_console_msg :: String , cpa_eps_file :: FilePath , cpa_svg_file :: FilePath - , cpa_prim_constructor :: DRGB -> DPrimitive + , cpa_prim_constructor :: RGBi -> DPrimitive , cpa_prim_transformer :: DPrimitive -> DPrimitive } @@ -98,7 +98,7 @@ where pic = cpPicture (cpa_prim_constructor cpa) (cpa_prim_transformer cpa) -cpPicture :: (DRGB -> DPrimitive) -> (DPrimitive -> DPrimitive) -> DPicture +cpPicture :: (RGBi -> DPrimitive) -> (DPrimitive -> DPrimitive) -> DPicture cpPicture constr trafo = illustrateBounds light_blue $ illustrateControlPoints black $ transformed_prim @@ -109,85 +109,29 @@ -------------------------------------------------------------------------------- -rgbLabel :: DRGB -> DPrimitive +rgbLabel :: RGBi -> DPrimitive rgbLabel rgb = textlabel rgb "Wumpus!" zeroPt -rgbCircle :: DRGB -> DPrimitive +rgbCircle :: RGBi -> DPrimitive rgbCircle rgb = ellipse rgb 60 60 zeroPt -rgbEllipse :: DRGB -> DPrimitive +rgbEllipse :: RGBi -> DPrimitive rgbEllipse rgb = ellipse rgb 60 30 zeroPt -rgbPath :: DRGB -> DPrimitive +rgbPath :: RGBi -> DPrimitive rgbPath rgb = ostroke rgb $ dog_kennel - - -------------------------------------------------------------------------------- --- Hughes Lists for Hughes Paths - --- At some point /wumpus-extra/ will have code along these lines... - -type H a = [a] -> [a] - -emptyH :: H a -emptyH = id - - --- NOTE - this is redundant if we want consH --- rather than snocH. --- --- Some of the drawings do appear to have problems with --- Z-order so we'll keep it for now. --- -consH :: a -> H a -> H a -consH a hf = hf . (a:) - -toListH :: H a -> [a] -toListH = ($ []) - - - --------------------------------------------------------------------------------- --- /Hughes/ paths - - -type HPath u = (Point2 u, H (PathSegment u)) - -makePath :: HPath u -> Path u -makePath (s,hf) = path s (toListH hf) - - -start_path :: (u,u) -> HPath u -start_path (x,y) = (P2 x y, emptyH) - -infixl 6 `line_to`, `curve_to` - -line_to :: HPath u -> (u,u) -> HPath u -line_to (s,f) (x,y) = (s, lineTo (P2 x y) `consH` f) - -curve_to :: HPath u -> ((u,u),(u,u),(u,u)) -> HPath u -curve_to (s,f) ((c1x,c1y),(c2x,c2y),(ex,ey)) = - (s, curveTo (P2 c1x c1y) (P2 c2x c2y) (P2 ex ey) `consH` f) - - --- --- vertical (length) & horizontal (length) might --- be handy... --- --- But we would need to track current position, vis-a-vis a --- state monad, so this is taking things towards a big module. --- --- - --------------------------------------------------------------------------------- -- Demo - draw a dog kennel... -dog_kennel :: DPath -dog_kennel = makePath $ - start_path (0,0) `line_to` (0,60) `line_to` (40,100) - `line_to` (80,60) `line_to` (80,0) - `line_to` (60,0) `line_to` (60,30) - `curve_to` ((60,50), (50,60), (40,60)) - `curve_to` ((30,60), (20,50), (20,30)) - `line_to` (20,0) - + +dog_kennel :: DPrimPath +dog_kennel = path zeroPt [ lineTo (P2 0 60) + , lineTo (P2 40 100) + , lineTo (P2 80 60) + , lineTo (P2 80 0) + , lineTo (P2 60 0) + , lineTo (P2 60 30) + , curveTo (P2 60 50) (P2 50 60) (P2 40 60) + , curveTo (P2 30 60) (P2 20 50) (P2 20 30) + , lineTo (P2 20 0) + ]
demo/FontMetrics.hs view
@@ -17,15 +17,14 @@ writeSVG_latin1 "./out/font_metrics.svg" metrics_pic --peru :: PSRgb-peru = RGB3 0.804 0.522 0.247+peru :: RGBi+peru = RGBi 205 133 63 -plum :: PSRgb-plum = RGB3 0.867 0.627 0.867+plum :: RGBi+plum = RGBi 221 160 221 -black :: PSRgb-black = RGB3 0 0 0 +black :: RGBi+black = RGBi 0 0 0 courier_attr :: FontAttr courier_attr = FontAttr 48 (FontFace "Courier" "Courier New" SVG_REGULAR)@@ -34,7 +33,7 @@ metrics_pic = char_pic `picOver` lines_pic lines_pic :: DPicture-lines_pic = frameMulti $ +lines_pic = frame $ [ ascender_line, numeral_line, xheight_line, baseline, descender_line ] where descender_pos = 0 - courier48_descender_depth@@ -48,7 +47,7 @@ char_pic :: Picture Double-char_pic = frameMulti $ zipWith ($) chars (iterate (.+^ hvec 32) zeroPt)+char_pic = frame $ zipWith ($) chars (iterate (.+^ hvec 32) zeroPt) where chars = (map letter "ABXabdgjxy12") ++ [agraveU] @@ -64,10 +63,10 @@ letter ch pt = textlabel (black, courier_attr) [ch] pt -vertLine :: DRGB -> Double -> DPoint2 -> DPrimitive+vertLine :: RGBi -> Double -> DPoint2 -> DPrimitive vertLine rgb height pt = ostroke rgb $ vertexPath [pt, pt .+^ vvec height] -haxis :: DRGB -> PtSize -> DPrimitive+haxis :: RGBi -> PtSize -> DPrimitive haxis rgb ypos = ostroke (rgb, dash_attr) $ vertexPath [ pt, pt .+^ hvec 440 ] where
demo/LabelPic.hs view
@@ -12,10 +12,10 @@ drawBounds :: (Floating u, Real u, FromPtSize u) => Picture u -> Picture u-drawBounds p = p `picOver` (frame $ cstroke () ph) +drawBounds p = p `picOver` (frame [cstroke () ph]) where ph = vertexPath $ [bl,br,tr,tl]- (bl,br,tr,tl) = corners $ boundary p+ (bl,br,tr,tl) = boundaryCorners $ boundary p -- | The center of a picture.@@ -28,22 +28,21 @@ -------------------------------------------------------------------------------- --peru :: PSRgb-peru = RGB3 0.804 0.522 0.247+peru :: RGBi+peru = RGBi 205 133 63 -plum :: PSRgb-plum = RGB3 0.867 0.627 0.867+plum :: RGBi+plum = RGBi 221 160 221 -black :: PSRgb-black = RGB3 0 0 0 +black :: RGBi+black = RGBi 0 0 0 lbl1 :: Picture Double lbl1 = line1 `picBeside` line2 where- line1 = frame (textlabel attrs "Hello" zeroPt)- line2 = frame (textlabel attrs "World" zeroPt)+ line1 = frame [textlabel attrs "Hello" zeroPt]+ line2 = frame [textlabel attrs "World" zeroPt] attrs = (peru, FontAttr 12 (FontFace "Helvetica" "Helvetica" SVG_REGULAR)) @@ -90,8 +89,8 @@ bigB = bigLetter peru 'B' bigT = bigLetter plum 'T' -bigLetter :: PSRgb -> Char -> Picture Double-bigLetter col ch = uniformScale 5 $ frame $ textlabel attrs [ch] zeroPt+bigLetter :: RGBi -> Char -> Picture Double+bigLetter col ch = uniformScale 5 $ frame [textlabel attrs [ch] zeroPt] where attrs = (col, FontAttr 12 (FontFace "Helvetica" "Helvetica" SVG_REGULAR))
demo/MultiPic.hs view
@@ -18,7 +18,7 @@ pic1 :: DPicture-pic1 = uniformScale 2 $ frameMulti $ +pic1 = uniformScale 2 $ frame $ [ ellipse blue 10 10 zeroPt , ellipse red 10 10 (P2 40 40) , ztextlabel "Wumpus!" (P2 40 20)@@ -26,7 +26,7 @@ ] -square :: (Num u, Ord u) => DRGB -> u -> Point2 u -> Primitive u+square :: (Num u, Ord u) => RGBi -> u -> Point2 u -> Primitive u square rgb sidelen bl = fill rgb $ vertexPath $ [bl, bl .+^ hvec sidelen, bl .+^ V2 sidelen sidelen, bl .+^ vvec sidelen]
demo/Rotated.hs view
@@ -28,7 +28,7 @@ pic1 :: DPicture-pic1 = illustrateBounds grey $ frameMulti $ +pic1 = illustrateBounds grey $ frame $ [ rotatePrimitive deg45 $ square blue 50 (P2 100 100) , square red 50 (P2 100 100) ]@@ -37,7 +37,7 @@ -- both ellipses should share the same center pic2 :: DPicture-pic2 = illustrateBounds grey $ frameMulti $ +pic2 = illustrateBounds grey $ frame $ [ rotatePrimitive deg45 $ ellipseHH blue 25 (P2 100 100) , ellipseHH red 25 (P2 100 100) ]@@ -45,26 +45,26 @@ -- both labels should share the same bottom left corner pic3 :: DPicture-pic3 = illustrateBounds grey $ frameMulti $ +pic3 = illustrateBounds grey $ frame $ [ rotatePrimitive deg45 $ label blue (P2 100 100) , label red (P2 100 100) ] -square :: (Num u, Ord u) => DRGB -> u -> Point2 u -> Primitive u+square :: (Num u, Ord u) => RGBi -> u -> Point2 u -> Primitive u square rgb sidelen bl = fill rgb $ vertexPath $ [bl, bl .+^ hvec sidelen, bl .+^ V2 sidelen sidelen, bl .+^ vvec sidelen] -ellipseHH :: Fractional u => DRGB -> u -> Point2 u -> Primitive u+ellipseHH :: Fractional u => RGBi -> u -> Point2 u -> Primitive u ellipseHH rgb radius ctr = ellipse rgb radius (0.5*radius) ctr -label :: Num u => DRGB -> Point2 u -> Primitive u+label :: Num u => RGBi -> Point2 u -> Primitive u label rgb bl = textlabel rgb "Wumpus" bl -grey :: DRGB-grey = iRGB3 176 197 223+grey :: RGBi+grey = RGBi 176 197 223
demo/Scaled.hs view
@@ -23,7 +23,7 @@ -- both squares should share the bottom left corner pic1 :: DPicture-pic1 = illustrateBounds grey $ frameMulti $ +pic1 = illustrateBounds grey $ frame $ [ uniformScalePrimitive 0.5 $ square blue 50 (P2 100 100) , square red 50 (P2 100 100) ]@@ -31,7 +31,7 @@ -- both ellipses should share the same center pic2 :: DPicture-pic2 = illustrateBounds grey $ frameMulti $ +pic2 = illustrateBounds grey $ frame $ [ uniformScalePrimitive 0.5 $ ellipseHH blue 25 (P2 100 100) , ellipseHH red 25 (P2 100 100) ]@@ -39,25 +39,25 @@ -- both labels should share the same bottom left corner pic3 :: DPicture-pic3 = illustrateBounds grey $ frameMulti $+pic3 = illustrateBounds grey $ frame $ [ uniformScalePrimitive 0.5 $ label blue (P2 100 100) , label red (P2 100 100) ] -square :: (Num u, Ord u) => DRGB -> u -> Point2 u -> Primitive u+square :: (Num u, Ord u) => RGBi -> u -> Point2 u -> Primitive u square rgb sidelen bl = fill rgb $ vertexPath $ [bl, bl .+^ hvec sidelen, bl .+^ V2 sidelen sidelen, bl .+^ vvec sidelen] -ellipseHH :: Fractional u => DRGB -> u -> Point2 u -> Primitive u+ellipseHH :: Fractional u => RGBi -> u -> Point2 u -> Primitive u ellipseHH rgb radius ctr = ellipse rgb radius (0.5*radius) ctr -label :: Num u => DRGB -> Point2 u -> Primitive u+label :: Num u => RGBi -> Point2 u -> Primitive u label rgb bl = textlabel rgb "Wumpus" bl -grey :: DRGB-grey = iRGB3 176 197 223+grey :: RGBi+grey = RGBi 176 197 223
demo/Translated.hs view
@@ -22,38 +22,38 @@ -- both squares should share the bottom left coord... pic1 :: DPicture-pic1 = illustrateBounds grey $ frameMulti $ +pic1 = illustrateBounds grey $ frame $ [ translatePrimitive 100 10 $ square blue 50 (P2 100 100) , square red 50 (P2 100 100) ] pic2 :: DPicture-pic2 = illustrateBounds grey $ frameMulti $ +pic2 = illustrateBounds grey $ frame $ [ translatePrimitive 100 10 $ ellipseHH blue 25 (P2 100 100) , ellipseHH red 25 (P2 100 100) ] pic3 :: DPicture-pic3 = illustrateBounds grey $ frameMulti $ +pic3 = illustrateBounds grey $ frame $ [ translatePrimitive 100 10 $ label blue (P2 100 100) , label red (P2 100 100) ] -square :: (Num u, Ord u) => DRGB -> u -> Point2 u -> Primitive u+square :: (Num u, Ord u) => RGBi -> u -> Point2 u -> Primitive u square rgb sidelen bl = fill rgb $ vertexPath $ [bl, bl .+^ hvec sidelen, bl .+^ V2 sidelen sidelen, bl .+^ vvec sidelen] -ellipseHH :: Fractional u => DRGB -> u -> Point2 u -> Primitive u+ellipseHH :: Fractional u => RGBi -> u -> Point2 u -> Primitive u ellipseHH rgb radius ctr = ellipse rgb radius (0.5*radius) ctr -label :: Num u => DRGB -> Point2 u -> Primitive u+label :: Num u => RGBi -> Point2 u -> Primitive u label rgb bl = textlabel rgb "Wumpus" bl -grey :: DRGB-grey = iRGB3 176 197 223+grey :: RGBi+grey = RGBi 176 197 223
+ demo/ZOrderPic.hs view
@@ -0,0 +1,35 @@+{-# OPTIONS -Wall #-}++module ZOrderPic where++import Wumpus.Core+import Wumpus.Core.Colour++import Data.AffineSpace -- package: vector-space++import System.Directory++main :: IO ()+main = do + createDirectoryIfMissing True "./out/"+ writeEPS_latin1 "./out/zorder01.eps" combined_pic+ writeSVG_latin1 "./out/zorder01.svg" combined_pic+++combined_pic :: DPicture+combined_pic = multi [pic1,pic2]++pic1 :: DPicture+pic1 = frame $ prim_list zeroPt++pic2 :: DPicture +pic2 = multi $ map (\a -> frame [a]) $ prim_list (P2 200 0)++++prim_list :: DPoint2 -> [DPrimitive]+prim_list = sequence [ ellipse red 20 20+ , \p -> ellipse green 20 20 (p .+^ hvec 20)+ , \p -> ellipse blue 20 20 (p .+^ hvec 40)+ ]+
doc-src/Guide.lhs view
@@ -164,25 +164,22 @@ Once labels and paths are assembled as a \emph{Picture} they are transformable with the usual affine transformations (scaling, -rotation, translation) and multiple pictures can be composed with -the operations provided by the \texttt{PictureLanguage} module. -The operations should be largely familiar from pretty-printing -libraries although here they are extended to 2 dimensions. +rotation, translation). Once assembled into pictures graphics properties (e.g. colour) are opaque - it is not possible to write a transformation function that turns a picture blue. In some ways this is a limitation - for instance, the \texttt{Diagrams} library appears to support -some notion of attribute overriding; however it is conceptually -simple. If one wanted to make blue arrows or red arrows with -\wumpuscore one would make colour a parameter of the arrow -creating function. +some notion of attribute overriding; however it does keep +\wumpuscore conceptually simple. If one wanted to draw blue or red +arrows with \wumpuscore, one would make drawing colour a parameter +of the arrow creation function. %----------------------------------------------------------------- \section{Affine transformations} %----------------------------------------------------------------- -For affine transformations Wumpus uses the \texttt{Matrix3} data +For affine transformations Wumpus uses the \texttt{Matrix3'3} data type to represent 3x3 matrices in row-major form. The constructor \texttt{(M3'3 a b c d e f g h i)} builds this matrix: @@ -220,7 +217,7 @@ not be very helpful in practice. Internally \wumpuscore only performs the transformation on the pictures bounding box - it needs to do this so transformed pictures can still be composed -with the picture language operations. +with the \texttt{picBeside} combinator. PostScript uses column-major form and uses a six element matrix rather than a nine element one. The translation matrix above @@ -231,7 +228,7 @@ \end{verbatim} Similarly, it would be communicated to SVG via a -\texttt{<g ...> </g>} element: +\texttt{group} element: \begin{verbatim} <g transform="matrix(1.0, 0.0, 0.0, 1.0, 10.0, 20.0)"> ... </g> @@ -239,11 +236,12 @@ For efficiency reasons \wumpuscore supports some transformations on Primitives. These are not affine transformations as Primitives -are not in an affine frame until they are lifted to Pictures. -For Paths, all the transformations are precomputed before the -output is generated. Unfortunately scaling and rotation cannot be -precomputed for labels and ellipses, so matrix operations are -generated in the PostScript and SVG output. +are not in an affine frame until they are lifted to Pictures +(Primitives have no notion of origin). For Paths, all the +transformations are precomputed before the output is generated. +Unfortunately scaling and rotation cannot be precomputed for +labels and ellipses, so matrix operations are generated in the +PostScript and SVG output. %----------------------------------------------------------------- @@ -255,20 +253,21 @@ label's string rather than the metrics of the individual letters encoded in the font. Accessing the glyph metrics in a font would require a font loader to read TrueType font files. This would be -a significant effort, probably larger than the effort put into -\wumpuscore itself; for \wumpuscore's intended use - producing -diagrams and pictures rather than high quality text - its -primitive font handling is not such a draw back. +a significant development effort, probably larger than the effort +put into \wumpuscore itself; for \wumpuscore's intended use - +producing diagrams and pictures rather than high quality text - +its primitive font handling is not such a draw back. In both PostScript and SVG mis-named fonts can cause somewhat inscrutable printing anomalies - usually falling back to a default -font but not always. PostScript may do no subsequent drawing after -a font load error. \wumpuscore uses @scalefont@ in the generated -PostScript, this semingly works for any integer size and not just -the regular font sizes (10, 12, 18, 24, 36). Older versions of -\wumpuscore mention that using non-standard sizes may cause font -loading problems, however this does not appear to be the case. +font but not always. At worst, PostScript may do no subsequent +drawing after a font load error. \wumpuscore uses @scalefont@ in +the generated PostScript, this semingly works for any integer size +and not just the regular font sizes (10, 12, 18, 24, 36). Older +versions of \wumpuscore mention that using non-standard sizes may +cause font loading problems, however this does not appear to be +the case. The following table lists PostScript fonts and their SVG
doc-src/Makefile view
@@ -9,3 +9,5 @@ dvipdfm -o ./out/Guide.pdf ./out/Guide.dvi cp ./out/Guide.pdf ../doc +worldframe: WorldFrame.hs+ runhaskell -i../src WorldFrame.hs
doc-src/WorldFrame.hs view
@@ -11,9 +11,9 @@ world_frame :: DPicture world_frame = uniformScale 0.75 $ - frameMulti [ ogin, btm_right, top_left, top_right- , x_axis, y_axis, line1- ]+ frame [ ogin, btm_right, top_left, top_right+ , x_axis, y_axis, line1+ ] where ogin = makeLabelPrim "(0,0)" (P2 0 0) btm_right = makeLabelPrim "(100,0)" (P2 96 0)@@ -29,7 +29,7 @@ makeLabelPrim :: String -> DPoint2 -> DPrimitive makeLabelPrim = textlabel attrs where- attrs = (black, FontAttr "Helvetica" "Helvetica" SVG_REGULAR 10)+ attrs = (black, FontAttr 10 (FontFace "Helvetica" "Helvetica" SVG_REGULAR)) makeLinePrim :: Double -> DPoint2 -> DPoint2 -> DPrimitive makeLinePrim lw a b = ostroke attrs $ path a [lineTo b]
doc/Guide.pdf view
binary file changed (57135 → 56990 bytes)
src/Wumpus/Core.hs view
@@ -8,7 +8,7 @@ -- -- Maintainer : Stephen Tetley <stephen.tetley@gmail.com> -- Stability : unstable --- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- Common interface to Wumpus.Core. --@@ -75,7 +75,7 @@ import Wumpus.Core.Colour hiding ( black, white, red, green, blue ) import Wumpus.Core.FontSize import Wumpus.Core.Geometry-import Wumpus.Core.GraphicsState hiding ( CTM, ToCTM )+import Wumpus.Core.GraphicsState hiding ( GraphicsState ) import Wumpus.Core.OutputPostScript import Wumpus.Core.OutputSVG import Wumpus.Core.Picture
src/Wumpus/Core/AffineTrans.hs view
@@ -11,7 +11,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- Affine transformations. -- @@ -31,7 +31,7 @@ -- -- To generate efficient PostScript, Wumpus relies on the matrix -- representations of the affine transformations being invertible.--- Please do not scale elements by zero.+-- Do not scale elements by zero! -- -------------------------------------------------------------------------------- @@ -77,7 +77,8 @@ -------------------------------------------------------------------------------- -- Affine transformations --- | Apply a matrix trasnformation directly.+-- | Apply a matrix transformation directly.+-- class Transform t where transform :: u ~ DUnit t => Matrix3'3 u -> t -> t @@ -103,6 +104,7 @@ -- | Type class for rotation about a point.+-- class RotateAbout t where rotateAbout :: u ~ DUnit t => Radian -> Point2 u -> t -> t @@ -118,27 +120,29 @@ -- Scale -- | Type class for scaling.+-- class Scale t where scale :: u ~ DUnit t => u -> u -> t -> t instance Num u => Scale (Point2 u) where- scale x y = ((scalingMatrix x y) *#) + scale sx sy = ((scalingMatrix sx sy) *#) instance Num u => Scale (Vec2 u) where- scale x y = ((scalingMatrix x y) *#) + scale sx sy = ((scalingMatrix sx sy) *#) -------------------------------------------------------------------------------- -- Translate --- | Type class for translations.+-- | Type class for translation.+-- class Translate t where translate :: DUnit t -> DUnit t -> t -> t instance Num u => Translate (Point2 u) where- translate x y = ((translationMatrix x y) *#)+ translate dx dy (P2 x y) = P2 (x+dx) (y+dy) instance Num u => Translate (Vec2 u) where- translate x y = ((translationMatrix x y) *#)+ translate dx dy (V2 x y) = V2 (x+dx) (y+dy) -------------------------------------------------------------------------------- @@ -147,42 +151,52 @@ -- | Rotate by 30 degrees about the origin. +-- rotate30 :: Rotate t => t -> t rotate30 = rotate (pi/6) -- | Rotate by 30 degrees about the supplied point.+-- rotate30About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate30About = rotateAbout (pi/6) -- | Rotate by 45 degrees about the origin. +-- rotate45 :: Rotate t => t -> t rotate45 = rotate (pi/4) -- | Rotate by 45 degrees about the supplied point.+-- rotate45About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate45About = rotateAbout (pi/4) -- | Rotate by 60 degrees about the origin. +-- rotate60 :: Rotate t => t -> t rotate60 = rotate (2*pi/3) -- | Rotate by 60 degrees about the supplied point.+-- rotate60About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate60About = rotateAbout (2*pi/3) -- | Rotate by 90 degrees about the origin. +-- rotate90 :: Rotate t => t -> t rotate90 = rotate (pi/2) -- | Rotate by 90 degrees about the supplied point.+-- rotate90About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate90About = rotateAbout (pi/2) -- | Rotate by 120 degrees about the origin. +-- rotate120 :: Rotate t => t -> t rotate120 = rotate (4*pi/3) -- | Rotate by 120 degrees about the supplied point.+-- rotate120About :: (RotateAbout t, DUnit t ~ u) => Point2 u -> t -> t rotate120About = rotateAbout (4*pi/3) @@ -192,21 +206,25 @@ -- Common scalings -- | Scale both x and y dimensions by the same amount.+-- uniformScale :: (Scale t, DUnit t ~ u) => u -> t -> t uniformScale a = scale a a -- | Reflect in the X-plane about the origin.+-- reflectX :: (Num u, Scale t, DUnit t ~ u) => t -> t reflectX = scale (-1) 1 -- | Reflect in the Y-plane about the origin.+-- reflectY :: (Num u, Scale t, DUnit t ~ u) => t -> t reflectY = scale 1 (-1) ----------------------------------------------------------------------------------- translations+-- Translations -- | Translate by the x and y components of a vector.+-- translateBy :: (Translate t, DUnit t ~ u) => Vec2 u -> t -> t translateBy (V2 x y) = translate x y @@ -215,11 +233,13 @@ -- Translation and scaling -- | Reflect in the X plane that intersects the supplied point. +-- reflectXPlane :: (Num u, Scale t, Translate t, u ~ DUnit t) => Point2 u -> t -> t reflectXPlane (P2 x y) = translate x y . scale (-1) 1 . translate (-x) (-y) -- | Reflect in the Y plane that intersects the supplied point.+-- reflectYPlane :: (Num u, Scale t, Translate t, u ~ DUnit t) => Point2 u -> t -> t reflectYPlane (P2 x y) = translate x y . scale 1 (-1) . translate (-x) (-y)
src/Wumpus/Core/BoundingBox.hs view
@@ -9,7 +9,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- Bounding box with no notion of \'empty\'. --@@ -17,10 +17,6 @@ -- simplifies the implementation of pictures themselves and -- bounding boxes. -- --- WARNING - this module is no so tightly designed, with some --- of the functions seeming superfluous in hindsight. It is--- likely to change in the future.--- -------------------------------------------------------------------------------- module Wumpus.Core.BoundingBox @@ -35,32 +31,26 @@ -- * Operations , bbox , obbox- , union + , destBoundingBox+ , boundaryUnion , traceBoundary , retraceBoundary - , corners- , within+ , boundaryCorners+ , withinBoundary , boundaryWidth , boundaryHeight- , boundaryBottomLeft- , boundaryTopRight- , boundaryTopLeft- , boundaryBottomRight - , leftPlane- , rightPlane- , lowerPlane- , upperPlane + ) where import Wumpus.Core.AffineTrans+import Wumpus.Core.FormatCombinators import Wumpus.Core.Geometry--import Data.Semigroup+import Wumpus.Core.Utils ( PSUnit(..) ) -import Text.PrettyPrint.Leijen hiding ( width )+import Data.Semigroup -- package: algebra @@ -87,11 +77,12 @@ -- BBox is NOT monoidal - it\'s much simpler that way. instance Ord u => Semigroup (BoundingBox u) where- append = union+ append = boundaryUnion -instance Pretty u => Pretty (BoundingBox u) where- pretty (BBox p0 p1) = text "|_" <+> pretty p0 <+> pretty p1 <+> text "_|" +instance PSUnit u => Format (BoundingBox u) where+ format (BBox p0 p1) = parens (text "BBox" <+> text "ll=" <> format p0 + <+> text "ur=" <> format p1) --------------------------------------------------------------------------------@@ -99,10 +90,27 @@ type instance DUnit (BoundingBox u) = u +pointTransform :: (Num u , Ord u)+ => (Point2 u -> Point2 u) -> BoundingBox u -> BoundingBox u+pointTransform fn bb = traceBoundary $ map fn $ [bl,br,tr,tl]+ where + (bl,br,tr,tl) = boundaryCorners bb+++instance (Num u, Ord u) => Transform (BoundingBox u) where+ transform mtrx = pointTransform (mtrx *#)++instance (Real u, Floating u) => Rotate (BoundingBox u) where+ rotate theta = pointTransform (rotate theta)++instance (Real u, Floating u) => RotateAbout (BoundingBox u) where+ rotateAbout theta pt = pointTransform (rotateAbout theta pt)+ instance (Num u, Ord u) => Scale (BoundingBox u) where- scale x y bb = traceBoundary $ map (scale x y) $ [bl,br,tr,tl]- where (bl,br,tr,tl) = corners bb+ scale sx sy = pointTransform (scale sx sy) +instance (Num u, Ord u) => Translate (BoundingBox u) where+ translate dx dy = pointTransform (translate dx dy) --------------------------------------------------------------------------------@@ -111,16 +119,8 @@ -- | Type class extracting the bounding box of an object - -- Picture, Path etc. ---class Boundary a where- boundary :: DUnit a ~ u => a -> BoundingBox u --------------------------------------------------------------------------------------instance Pointwise (BoundingBox u) where- type Pt (BoundingBox u) = Point2 u- pointwise f (BBox bl tr) = BBox (f bl) (f tr)+class Boundary t where+ boundary :: u ~ DUnit t => t -> BoundingBox u --------------------------------------------------------------------------------@@ -143,12 +143,22 @@ obbox :: Num u => u -> u -> BoundingBox u obbox w h = BBox zeroPt (P2 w h) +-- | Destructor for BoundingBox.+--+-- Assembles a four-tuple @ (ll_x, ll_y, ur_x, ur_y) @.+-- +-- Arguably this is easier to pattern match upon as it removes a +-- layer of nesting.+--+destBoundingBox :: BoundingBox u -> (u,u,u,u)+destBoundingBox (BBox (P2 llx lly) (P2 urx ury)) = (llx, lly, urx, ury) + -- | The union of two bounding boxes. This is also the @append@ -- of BoundingBox\'s @Semigroup@ instance. ---union :: Ord u => BoundingBox u -> BoundingBox u -> BoundingBox u-BBox ll ur `union` BBox ll' ur' = BBox (minPt ll ll') (maxPt ur ur')+boundaryUnion :: Ord u => BoundingBox u -> BoundingBox u -> BoundingBox u+BBox ll ur `boundaryUnion` BBox ll' ur' = BBox (minPt ll ll') (maxPt ur ur') -- | Trace a list of points, retuning the BoundingBox that -- includes them.@@ -170,22 +180,22 @@ -- retraceBoundary :: (Num u, Ord u) => (Point2 u -> Point2 u) -> BoundingBox u -> BoundingBox u-retraceBoundary f = traceBoundary . map f . fromCorners . corners+retraceBoundary f = traceBoundary . map f . fromCorners . boundaryCorners where fromCorners (bl,br,tr,tl) = [bl,br,tr,tl] -- | Generate all the corners of a bounding box, counter-clock -- wise from the bottom left, i.e. @(bl, br, tr, tl)@.-corners :: BoundingBox u -> (Point2 u, Point2 u, Point2 u, Point2 u)-corners (BBox bl@(P2 x0 y0) tr@(P2 x1 y1)) = (bl, br, tr, tl) where+boundaryCorners :: BoundingBox u -> (Point2 u, Point2 u, Point2 u, Point2 u)+boundaryCorners (BBox bl@(P2 x0 y0) tr@(P2 x1 y1)) = (bl, br, tr, tl) where br = P2 x1 y0 tl = P2 x0 y1 -- | Within test - is the supplied point within the bounding box? ---within :: Ord u => Point2 u -> BoundingBox u -> Bool-within p (BBox ll ur) = (minPt p ll) == ll && (maxPt p ur) == ur+withinBoundary :: Ord u => Point2 u -> BoundingBox u -> Bool+withinBoundary p (BBox ll ur) = (minPt p ll) == ll && (maxPt p ur) == ur -- | Extract the width of a bounding box. --@@ -196,54 +206,6 @@ -- boundaryHeight :: Num u => BoundingBox u -> u boundaryHeight (BBox (P2 _ ymin) (P2 _ ymax)) = ymax - ymin--------------------------------------------------------------------------------------- Points on the boundary---- | Extract the bottom-left corner of the bounding box.-boundaryBottomLeft :: BoundingBox u -> Point2 u-boundaryBottomLeft (BBox p0 _ ) = p0---- | Extract the top-right corner of the bounding box.-boundaryTopRight :: BoundingBox u -> Point2 u-boundaryTopRight (BBox _ p1) = p1---- | Extract the top-left corner of the bounding box.-boundaryTopLeft :: BoundingBox u -> Point2 u-boundaryTopLeft (BBox (P2 x _) (P2 _ y)) = P2 x y---- | Extract the bottom-right corner of the bounding box.-boundaryBottomRight :: BoundingBox u -> Point2 u-boundaryBottomRight (BBox (P2 _ y) (P2 x _)) = P2 x y----------------------------------------------------------------------------------------- /planes/ on the bounding box---- Are these really worthwhile ? ...---- | Extract the unit of the left vertical plane.-leftPlane :: BoundingBox u -> u-leftPlane (BBox (P2 l _) _) = l---- | Extract the unit of the right vertical plane.-rightPlane :: BoundingBox u -> u-rightPlane (BBox _ (P2 r _)) = r---- | Extract the unit of the lower horizontal plane.-lowerPlane :: BoundingBox u -> u-lowerPlane (BBox (P2 _ l) _) = l---- | Extract the unit of the upper horizontal plane.-upperPlane :: BoundingBox u -> u-upperPlane (BBox _ (P2 _ u)) = u--
src/Wumpus/Core/Colour.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# OPTIONS -Wall #-} --------------------------------------------------------------------------------@@ -8,288 +6,88 @@ -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 ----- Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more--------- RGB, HSB, Gray colour types, and conversions between them.------ Internally Wumpus uses @RGB3 Double@ with range [0.0, 1..0] --- as the colour type. All colour values in the generated SVG or --- PostScript files will be RGB.+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC ----- Colour have Num instances for convenience, though the --- operations of @Data.VectorSpace@ (instances also defined) --- seem more approriate.+-- Colour represented as RGB with each component in the range +-- [0..255].+-- +-- Note - the predifined colours are hidden when importing the+-- /top-level/ module @Wumpus.Core@, import this module directly+-- to use them. -- -------------------------------------------------------------------------------- -module Wumpus.Core.Colour - (- -- * Colour types- RGB3(..)- , DRGB- , HSB3(..)- , DHSB- , Gray(..)- , DGray-- -- * Utility constructor- , iRGB3- , iHSB3- , iGray- - -- * Operations- , rgb2hsb- , hsb2rgb+module Wumpus.Core.Colour+ ( - , rgb2gray- , gray2rgb+ -- * RGB colour type + RGBi(..) - , hsb2gray- , gray2hsb- -- * Predefined colours , black , white , red , green , blue-+ ) where -import Wumpus.Core.Utils---import Data.VectorSpace+import Wumpus.Core.FormatCombinators --- | Red-Green-Blue - no alpha.----data RGB3 a = RGB3 !a !a !a- deriving (Eq,Show)+import Data.Word --- | RGB representated by Double - values should be in the range--- 0.0 to 1.0. +-- | Colours levels are in the range [0..255] -- --- 1.0 represents full saturation, for instance red is --- 1.0, 0.0, 0.0.----type DRGB = RGB3 Double----- | Hue-Saturation-Brightness.+-- Note - this is the format used by SVG, whereas PostScript uses +-- [0..1]. ---data HSB3 a = HSB3 !a !a !a - deriving (Eq,Show)---- | HSB represented by Double - values should be in the range--- 0.0 to 1.0.+-- It is more efficient to prefer SVG here. ---type DHSB = HSB3 Double ---- | Gray scale.--- -newtype Gray a = Gray a- deriving (Eq,Num,Fractional,Ord,Show)---- | Gray represented by a Double - values should be in the range--- 0.0 (black) to 1.0 (white).-type DGray = Gray Double+data RGBi = RGBi !Word8 !Word8 !Word8+ deriving (Eq,Ord,Show) ----------------------------------------------------------------------------------- Num instances--instance Num a => Num (RGB3 a) where- (+) (RGB3 a b c) (RGB3 x y z) = RGB3 (a+x) (b+y) (c+z)- (-) (RGB3 a b c) (RGB3 x y z) = RGB3 (a-x) (b-y) (c-z)- (*) (RGB3 a b c) (RGB3 x y z) = RGB3 (a*x) (b*y) (c*z)- abs (RGB3 a b c) = RGB3 (abs a) (abs b) (abs c)- negate (RGB3 a b c) = RGB3 (negate a) (negate b) (negate c)- signum (RGB3 a b c) = RGB3 (signum a) (signum b) (signum c)- fromInteger i = RGB3 (fromInteger i) (fromInteger i) (fromInteger i)---instance Num a => Num (HSB3 a) where- (+) (HSB3 a b c) (HSB3 x y z) = HSB3 (a+x) (b+y) (c+z)- (-) (HSB3 a b c) (HSB3 x y z) = HSB3 (a-x) (b-y) (c-z)- (*) (HSB3 a b c) (HSB3 x y z) = HSB3 (a*x) (b*y) (c*z)- abs (HSB3 a b c) = HSB3 (abs a) (abs b) (abs c)- negate (HSB3 a b c) = HSB3 (negate a) (negate b) (negate c)- signum (HSB3 a b c) = HSB3 (signum a) (signum b) (signum c)- fromInteger i = HSB3 (fromInteger i) (fromInteger i) (fromInteger i)---- Num (Gray a) derived---instance Fractional a => Fractional (RGB3 a) where- (/) (RGB3 a b c) (RGB3 x y z) = RGB3 (a/x) (b/y) (c/z)- recip (RGB3 a b c) = RGB3 (recip a) (recip b) (recip c)- fromRational a = RGB3 (fromRational a) (fromRational a) (fromRational a)+-- instances -instance Fractional a => Fractional (HSB3 a) where- (/) (HSB3 a b c) (HSB3 x y z) = HSB3 (a/x) (b/y) (c/z)- recip (HSB3 a b c) = HSB3 (recip a) (recip b) (recip c)- fromRational a = HSB3 (fromRational a) (fromRational a) (fromRational a)+instance Format RGBi where+ format (RGBi 0 0 0) = text "*black*"+ format (RGBi 255 255 255) = text "*white*"+ format (RGBi r g b) = integral r <> comma <> integral g + <> comma <> integral b --- Fractional (Gray a) derived- ----------------------------------------------------------------------------------- Instances for VectorSpace - -instance Num a => AdditiveGroup (RGB3 a) where- zeroV = RGB3 0 0 0- (^+^) = (+)- negateV = negate- -instance Num a => AdditiveGroup (HSB3 a) where- zeroV = HSB3 0 0 0- (^+^) = (+)- negateV = negate -instance Num a => AdditiveGroup (Gray a) where- zeroV = Gray 0 -- black- (^+^) = (+)- negateV = negate---instance Num a => VectorSpace (RGB3 a) where- type Scalar (RGB3 a) = a- s *^ (RGB3 a b c) = RGB3 (s*a) (s*b) (s*c)--instance Num a => VectorSpace (HSB3 a) where- type Scalar (HSB3 a) = a- s *^ (HSB3 a b c) = HSB3 (s*a) (s*b) (s*c)--instance Num a => VectorSpace (Gray a) where- type Scalar (Gray a) = a- s *^ (Gray a) = Gray (s*a)-------------------------------------------------------------------------------------- Utility constructors---- | 'iRGB3' : @ red -> green -> blue -> rgb @--- --- Create an RGB colour with intergers in the range [0..255].--- --- 255 represents full sturation so red will be @ 255 0 0 @.------ Integer values above 255 will be clamped to 255, similarly--- values below 0 will be clamped to 0.--- -iRGB3 :: (Fractional a, Ord a) => Int -> Int -> Int -> RGB3 a-iRGB3 r g b = RGB3 (rescaleZeroOne r) (rescaleZeroOne g) (rescaleZeroOne b)--iHSB3 :: (Fractional a, Ord a) => Int -> Int -> Int -> HSB3 a-iHSB3 h s b = HSB3 (rescaleZeroOne h) (rescaleZeroOne s) (rescaleZeroOne b)--iGray :: (Fractional a, Ord a) => Int -> Gray a-iGray i = Gray $ rescaleZeroOne i--rescaleZeroOne :: (Fractional a, Ord a) => Int -> a-rescaleZeroOne a = rescale (0,255.0) (0,1.0) (clamp 0 255 $ fromIntegral a)------------------------------------------------------------------------------------- Operations---vE :: DRGB-vE = RGB3 1 1 1---- Acknowledgment - the conversion functions are derived from--- the documentation to Dr. Uwe Kern's xcolor LaTeX package----- | Covert RGB \[0,1\] to HSB \[0,1\].-rgb2hsb :: DRGB -> DHSB-rgb2hsb (RGB3 r g b) = HSB3 hue sat bri- where- x = max3 r g b- y = med3 r g b- z = min3 r g b-- bri = x-- (sat,hue) = if x==z then (0,0) else ((x-z)/x, f $ (x-y)/(x-z))- - f n | r >= g && g >= b = (1/6) * (1-n) - | g >= r && r >= b = (1/6) * (1+n)- | g >= b && b >= r = (1/6) * (3-n)- | b >= g && g >= r = (1/6) * (3+n)- | b >= r && r >= g = (1/6) * (5-n)- | otherwise = (1/6) * (5+n)----- | Covert HSB \[0,1\] to RGB \[0,1\].-hsb2rgb :: DHSB -> DRGB-hsb2rgb (HSB3 hue sat bri) = bri *^ (vE - (sat *^ fV))- where- i :: Int- i = floor $ (6 * hue)- f = (6 * hue) - fromIntegral i- fV | i == 0 = RGB3 0 (1-f) 1 - | i == 1 = RGB3 f 0 1- | i == 2 = RGB3 1 0 (1-f)- | i == 3 = RGB3 1 f 0- | i == 4 = RGB3 (1-f) 1 0- | i == 5 = RGB3 0 1 f- | otherwise = RGB3 0 1 1- --- | Covert RGB \[0,1\] to Gray \[0,1\].-rgb2gray :: DRGB -> DGray-rgb2gray (RGB3 r g b) = Gray $ 0.3 * r + 0.59 * g + 0.11 * b ----- | Covert Gray \[0,1\] to RGB \[0,1\].-gray2rgb :: DGray -> DRGB-gray2rgb (Gray a) = a *^ vE----- | Covert HSB \[0,1\] to Gray \[0,1\].-hsb2gray :: DHSB -> DGray-hsb2gray (HSB3 _ _ b) = Gray b ----- | Covert Gray \[0,1\] to HSB \[0,1\].-gray2hsb :: DGray -> DHSB-gray2hsb (Gray a) = HSB3 0 0 a-------------------------------------------------------------------------------------- -- Some colours -- There will be name clashes with the X11Colours / SVGColours. --- | Black - 0.0, 0.0, 0.0.+-- | Black - 0, 0, 0. ---black :: DRGB-black = RGB3 0 0 0+black :: RGBi+black = RGBi 0 0 0 --- | White - 1.0, 1.0, 1.0.+-- | White - 255, 255, 255. ---white :: DRGB-white = RGB3 1 1 1+white :: RGBi+white = RGBi 255 255 255 --- | Red - 1.0, 0.0, 0.0.+-- | Red - 255, 0, 0. ---red :: DRGB-red = RGB3 1 0 0+red :: RGBi+red = RGBi 255 0 0 --- | Green - 0.0, 1.0, 0.0.+-- | Green - 0, 255, 0. ---green :: DRGB -green = RGB3 0 1 0+green :: RGBi +green = RGBi 0 255 0 --- | Blue - 0.0, 0.0, 1.0.+-- | Blue - 0, 0, 255. ---blue :: DRGB-blue = RGB3 0 0 1-+blue :: RGBi+blue = RGBi 0 0 255
src/Wumpus/Core/FontSize.hs view
@@ -8,7 +8,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- Font size calculation for Label\'s and their bounding boxes. --
+ src/Wumpus/Core/FormatCombinators.hs view
@@ -0,0 +1,384 @@+{-# OPTIONS -Wall #-}++--------------------------------------------------------------------------------+-- |+-- Module : Wumpus.Core.FormatCombinators+-- Copyright : (c) Stephen Tetley 2010+-- License : BSD3+--+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC+--+-- Formatting combinators - pretty printers without the fitting.+--+--------------------------------------------------------------------------------++module Wumpus.Core.FormatCombinators+ (+ Doc+ , DocS+ , Format(..)+ , empty+ , showsDoc+ , (<>)+ , (<+>) + , vconcat+ , separate+ , hcat+ , hsep+ , vcat++ , text+ , char+ , int+ , integer+ , integral+ , float+ , double++ , space+ , comma+ , semicolon+ , line++ , fill++ , punctuate+ , enclose+ , squotes+ , dquotes+ , parens+ , brackets+ , braces+ , angles++ , lparen+ , rparen+ , lbracket+ , rbracket+ , lbrace+ , rbrace+ , langle+ , rangle++ , list+ , tupled+ , semiBraces++ , indent+ + ) where++import Data.Monoid+import Numeric++-- | Doc is a Join List ...+--+data Doc = Doc1 ShowS + | Join Doc Doc+ | Line+ | Indent !Int Doc +++type DocS = Doc -> Doc+++-- Join could be improved...+--+unDoc :: Doc -> ShowS+unDoc = step 0 id+ where+ step _ acc (Doc1 sf) = acc . sf+ step n acc (Join a b) = let acc' = step n acc a in step n acc' b+ step n acc Line = acc . showChar '\n' . indentS n+ step n acc (Indent i d) = step (n+i) (acc . (indentS i)) d+++indentS :: Int -> ShowS+indentS i | i < 1 = id+ | otherwise = showString $ replicate i ' '++runDoc :: Doc -> String+runDoc = ($ "") . unDoc+++instance Show Doc where+ show = runDoc++instance Monoid Doc where+ mempty = empty+ mappend = (<>)+++class Format a where format :: a -> Doc++--------------------------------------------------------------------------------+ +infixr 6 <>, <+>++++-- | Create an empty, zero length document.+--+empty :: Doc+empty = Doc1 id++-- | Create a document from a ShowS function.+--+showsDoc :: ShowS -> Doc+showsDoc = Doc1+++-- | Horizontally concatenate two documents with no space +-- between them.+-- +(<>) :: Doc -> Doc -> Doc+a <> b = Join a b +++-- | Horizontally concatenate two documents with a single space +-- between them.+-- +(<+>) :: Doc -> Doc -> Doc+a <+> b = Join a (Join space b)++-- | Vertical concatenate two documents with a line break.+-- +vconcat :: Doc -> Doc -> Doc+vconcat a b = a <> Line <> b++++separate :: Doc -> [Doc] -> Doc+separate _ [] = empty+separate sep (a:as) = step a as+ where+ step acc [] = acc+ step acc (x:xs) = step (acc <> sep <> x) xs++-- | Horizontally concatenate a list of documents with @(\<\>)@.+--+hcat :: [Doc] -> Doc+hcat = foldr (<>) empty++-- | Horizontally concatenate a list of documents with @(\<+\>)@.+--+hsep :: [Doc] -> Doc+hsep = separate space++-- | Vertically concatenate a list of documents, with a line +-- break between each doc.+--+vcat :: [Doc] -> Doc+vcat [] = empty+vcat (x:xs) = step x xs + where+ step acc (z:zs) = step (acc `vconcat` z) zs+ step acc [] = acc++-- | Create a document from a literal string.+-- +-- The string should not contain newlines (though this is not +-- enforced). +--+text :: String -> Doc+text = Doc1 . showString+++-- | Create a document from a literal character.+--+-- The char should not be a tab or newline. +--+char :: Char -> Doc+char = Doc1 . showChar++-- | Show the Int as a Doc.+--+-- > int = text . show+--+int :: Int -> Doc+int = Doc1 . showInt++-- | Show the Integer as a Doc.+--+integer :: Integer -> Doc+integer = Doc1 . showInt++-- | Show an \"integral value\" as a Doc via 'fromIntegral'.+--+integral :: Integral a => a -> Doc+integral = Doc1 . showInt++-- | Show the Float as a Doc.+--+float :: Double -> Doc+float = Doc1 . showFloat++-- | Show the Double as a Doc.+--+double :: Double -> Doc+double = Doc1 . showFloat+ +-- | Create a Doc containing a single space character.+--+space :: Doc+space = char ' '++-- | Create a Doc containing a comma, \",\".+--+comma :: Doc+comma = char ','++-- | Create a Doc containing a semi colon, \";\".+--+semicolon :: Doc+semicolon = char ';'++-- | Create a Doc containing newline, \"\\n\".+--+line :: Doc +line = char '\n'+++--------------------------------------------------------------------------------++-- | Fill a doc to the supplied length, padding the right-hand+-- side with spaces.+--+-- Note - this function is expensive - it unrolls the functional+-- representation of the String. +-- +-- Also it should only be used for single line Doc\'s.+-- +fill :: Int -> Doc -> Doc+fill i d = Doc1 (padr i ' ' $ unDoc d) ++padr :: Int -> Char -> ShowS -> ShowS+padr i c df = step (length $ df []) + where+ step len | len >= i = df+ | otherwise = df . showString (replicate (i-len) c)++--------------------------------------------------------------------------------++-- | Punctuate the Doc list with the separator, producing a Doc. +--+punctuate :: Doc -> [Doc] -> Doc+punctuate _ [] = empty+punctuate _ [x] = x+punctuate s (x:xs) = x <> s <> punctuate s xs+++-- | Enclose the final Doc within the first two.+--+-- There are no spaces between the documents:+--+-- > enclose l r d = l <> d <> r+--+enclose :: Doc -> Doc -> Doc -> Doc+enclose l r d = l <> d <> r++++-- | Enclose the Doc within single quotes.+--+squotes :: Doc -> Doc+squotes = enclose (char '\'') (char '\'')++-- | Enclose the Doc within double quotes.+--+dquotes :: Doc -> Doc+dquotes = enclose (char '"') (char '"')++-- | Enclose the Doc within parens @()@.+--+parens :: Doc -> Doc+parens = enclose lparen rparen++-- | Enclose the Doc within square brackets @[]@.+--+brackets :: Doc -> Doc+brackets = enclose lbracket rbracket++-- | Enclose the Doc within curly braces @{}@.+--+braces :: Doc -> Doc+braces = enclose lbrace rbrace++-- | Enclose the Doc within angle brackets @\<\>@.+--+angles :: Doc -> Doc+angles = enclose langle rangle++++-- | Create a Doc containing a left paren, \'(\'.+--+lparen :: Doc+lparen = char '('++-- | Create a Doc containing a right paren, \')\'.+--+rparen :: Doc+rparen = char ')'++-- | Create a Doc containing a left square bracket, \'[\'.+--+lbracket :: Doc+lbracket = char '['++-- | Create a Doc containing a right square bracket, \']\'.+--+rbracket :: Doc+rbracket = char ']'++-- | Create a Doc containing a left curly brace, \'{\'.+--+lbrace :: Doc+lbrace = char '{'++-- | Create a Doc containing a right curly brace, \'}\'.+--+rbrace :: Doc+rbrace = char '}'++-- | Create a Doc containing a left angle bracket, \'\<\'.+--+langle :: Doc+langle = char '<'++-- | Create a Doc containing a right angle bracket, \'\>\'.+--+rangle :: Doc+rangle = char '>'++-- | Comma separate the list of documents and enclose in square+-- brackets.+--+list :: [Doc] -> Doc+list = brackets . punctuate comma++-- | Comma separate the list of documents and enclose in parens.+--+tupled :: [Doc] -> Doc+tupled = parens . punctuate comma++-- | Separate the list with a semicolon and enclose in curly +-- braces.+--+semiBraces :: [Doc] -> Doc+semiBraces = braces . punctuate semicolon+++-- | Horizontally indent a Doc.+--+-- Note - this space-prefixes the Doc on /the current line/. It+-- does not indent subsequent lines if the Doc spans multiple +-- lines.+--+indent :: Int -> Doc -> Doc+indent i d | i < 1 = d+ | otherwise = Indent i d++
src/Wumpus/Core/Geometry.hs view
@@ -1,32 +1,31 @@ {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE FlexibleContexts #-} {-# OPTIONS -Wall #-} -------------------------------------------------------------------------------+-------------------------------------------------------------------------------- -- | -- Module : Wumpus.Core.Geometry -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 ----- Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC -- -- Objects and operations for 2D geometry. -- --- Vector, point, affine frame, 3x3 matrix, and radian --- representations, plus a type family @DUnit@ for parameterizing--- type classes with some /dimension/.+-- Vector, point, 3x3 matrix, and radian representations, +-- plus a type family @DUnit@ for parameterizing type classes +-- with some /dimension/. -- -------------------------------------------------------------------------------- -module Wumpus.Core.Geometry ++module Wumpus.Core.Geometry ( + -- * Type family DUnit @@ -35,17 +34,11 @@ , DVec2 , Point2(..) , DPoint2- , Frame2(..)- , DFrame2 , Matrix3'3(..) , DMatrix3'3- , Radian -- -- * Pointwise type class- , Pointwise(..)+ , Radian - -- * Matrix multiply type class , MatrixMult(..) -- * Vector operations@@ -60,18 +53,9 @@ -- * Point operations , zeroPt- , minPt , maxPt- , langle-- -- * Frame operations- , ortho- , displaceOrigin- , pointInFrame- , frame2Matrix- , matrix2Frame- , frameProduct- , standardFrame+ , minPt+ , lineDirection -- * Matrix contruction , identityMatrix@@ -86,79 +70,71 @@ , transpose -- * Radian operations- , req , toRadian , fromRadian , d2r , r2d , circularModulo - -- * Bezier curves , bezierArc , bezierCircle ) where -import Wumpus.Core.Utils ( PSUnit(..), oo ) +import Wumpus.Core.FormatCombinators+import Wumpus.Core.Utils -import Data.AffineSpace+import Data.AffineSpace -- package: vector-space import Data.VectorSpace -import Text.PrettyPrint.Leijen hiding ( langle ) -import Data.Function ( on )-import Data.Monoid + -------------------------------------------------------------------------------- -- | Some unit of dimension usually double.-+--+-- This very useful for reducing the kind of type classes to *.+-- +-- Doing this then allows constraints on the Unit type on the +-- instances rather than in the class declaration.+-- type family DUnit a :: * + -- Datatypes -- | 2D Vector - both components are strict. ---data Vec2 u = V2 !u !u+data Vec2 u = V2 + { vector_x :: !u + , vector_y :: !u+ } deriving (Eq,Show) type DVec2 = Vec2 Double ++ -- | 2D Point - both components are strict. -- -- Note - Point2 derives Ord so it can be used as a key in -- Data.Map etc. ---data Point2 u = P2 !u !u+data Point2 u = P2 + { point_x :: !u+ , point_y :: !u+ } deriving (Eq,Ord,Show) type DPoint2 = Point2 Double --- | A two dimensional frame.--- --- The components are the two basis vectors @e0@ and @e1@ and --- the origin @o@.------ Typically these names for the elements will be used:------ > Frame2 (V2 e0x e0y) (V2 e1x e1y) (P2 ox oy)--- --data Frame2 u = Frame2 (Vec2 u) (Vec2 u) (Point2 u)- deriving (Eq,Show)--type DFrame2 = Frame2 Double----- -- | 3x3 matrix, considered to be in row-major form. -- -- > (M3'3 a b c@@ -199,22 +175,20 @@ type DMatrix3'3 = Matrix3'3 Double - -- | Radian is represented with a distinct type. -- Equality and ordering are approximate where the epsilon -- is 0.0001. newtype Radian = Radian { getRadian :: Double } deriving (Num,Real,Fractional,Floating,RealFrac,RealFloat) - -------------------------------------------------------------------------------- -- Family instances type instance DUnit (Point2 u) = u type instance DUnit (Vec2 u) = u-type instance DUnit (Frame2 u) = u type instance DUnit (Matrix3'3 u) = u + -------------------------------------------------------------------------------- -- lifters / convertors @@ -222,7 +196,6 @@ lift2Vec2 op (V2 x y) (V2 x' y') = V2 (x `op` x') (y `op` y') - lift2Matrix3'3 :: (u -> u -> u) -> Matrix3'3 u -> Matrix3'3 u -> Matrix3'3 u lift2Matrix3'3 op (M3'3 a b c d e f g h i) (M3'3 m n o p q r s t u) = M3'3 (a `op` m) (b `op` n) (c `op` o) @@ -230,7 +203,6 @@ (g `op` s) (h `op` t) (i `op` u) - -------------------------------------------------------------------------------- -- instances @@ -245,24 +217,8 @@ instance Functor Matrix3'3 where- fmap f (M3'3 m n o p q r s t u) = - M3'3 (f m) (f n) (f o) (f p) (f q) (f r) (f s) (f t) (f u)----- Monoid---- Vectors have a sensible Monoid instance as addition, points don't--instance Num u => Monoid (Vec2 u) where- mempty = V2 0 0- mappend = lift2Vec2 (+) ----- Affine frames also have a sensible Monoid instance--instance (Num u, InnerSpace (Vec2 u)) => Monoid (Frame2 u) where- mempty = ortho zeroPt- mappend = frameProduct+ fmap f (M3'3 m n o p q r s t u) = + M3'3 (f m) (f n) (f o) (f p) (f q) (f r) (f s) (f t) (f u) -- Show@@ -289,6 +245,7 @@ signum = fmap signum fromInteger a = M3'3 a' a' a' a' a' a' a' a' a' where a' = fromInteger a +-------------------------------------------------------------------------------- -- Instances for Radian which are 'special'. instance Show Radian where@@ -300,32 +257,28 @@ compare a b | a `req` b = EQ | otherwise = getRadian a `compare` getRadian b ++ -------------------------------------------------------------------------------- -- Pretty printing -instance Pretty u => Pretty (Vec2 u) where- pretty (V2 a b) = angles (char '|' <+> pretty a <+> pretty b <+> char '|')--instance Pretty u => Pretty (Point2 u) where- pretty (P2 a b) = brackets (char '|' <+> pretty a <+> pretty b <+> char '|')+instance PSUnit u => Format (Vec2 u) where+ format (V2 a b) = parens (text "Vec" <+> dtruncFmt a <+> dtruncFmt b) -instance Pretty u => Pretty (Frame2 u) where- pretty (Frame2 e0 e1 o) = braces $- text "e0:" <> pretty e0- <+> text "e1:" <> pretty e1- <+> text "o:" <> pretty o+instance PSUnit u => Format (Point2 u) where+ format (P2 a b) = parens (dtruncFmt a <> comma <+> dtruncFmt b) -instance PSUnit u => Pretty (Matrix3'3 u) where- pretty (M3'3 a b c d e f g h i) = - matline a b c <$> matline d e f <$> matline g h i+instance PSUnit u => Format (Matrix3'3 u) where+ format (M3'3 a b c d e f g h i) = + vcat [matline a b c, matline d e f, matline g h i] where matline x y z = char '|' - <+> (hcat $ map (fill 12 . text . dtrunc) [x,y,z]) + <+> (hcat $ map (fill 12 . dtruncFmt) [x,y,z]) <+> char '|' -instance Pretty Radian where- pretty (Radian d) = double d <> text ":rad"+instance Format Radian where+ format (Radian d) = double d <> text ":rad" -------------------------------------------------------------------------------- -- Vector space instances@@ -342,12 +295,12 @@ -- scalar (dot / inner) product via the class InnerSpace-+-- -- This definition mandates UndecidableInstances, but this seems -- in line with Data.VectorSpace... -- -instance (Num u, InnerSpace u, Scalar u ~ u) +instance (Num u, InnerSpace u, u ~ Scalar u) => InnerSpace (Vec2 u) where (V2 a b) <.> (V2 a' b') = (a <.> a') ^+^ (b <.> b') @@ -368,38 +321,11 @@ type Scalar (Matrix3'3 u) = u s *^ m = fmap (s*) m --------------------------------------------------------------------------------- --- | Pointwise is a Functor like type class, except that the --- container/element relationship is defined via an associated --- type rather than a type parameter. This means that applied --- function must be type preserving.----class Pointwise sh where- type Pt sh :: *- pointwise :: (Pt sh -> Pt sh) -> sh -> sh---instance Pointwise (a -> a) where- type Pt (a->a) = a- pointwise f pf = \a -> pf (f a)--instance Pointwise a => Pointwise [a] where - type Pt [a] = Pt a- pointwise f pts = map (pointwise f) pts --instance Pointwise (Vec2 u) where- type Pt (Vec2 u) = Vec2 u- pointwise f v = f v--instance Pointwise (Point2 u) where- type Pt (Point2 u) = Point2 u- pointwise f pt = f pt-- -------------------------------------------------------------------------------- -- Matrix multiply + infixr 7 *# -- | Matrix multiplication - typically of points and vectors @@ -416,11 +342,13 @@ instance Num u => MatrixMult (Point2 u) where (M3'3 a b c d e f _ _ _) *# (P2 m n) = P2 (a*m+b*n+c*1) (d*m+e*n+f*1) + -------------------------------------------------------------------------------- -- Vectors --- | A synonym for the constructor 'V2' with a Num constraint on --- the arguments.++-- | 'vec' - a synonym for the constructor 'V2' with a Num +-- constraint on the arguments. -- -- Essentially superfluous, but it can be slightly more -- typographically pleasant when used in lists of vectors:@@ -445,14 +373,15 @@ vvec :: Num u => u -> Vec2 u vvec d = V2 0 d + -- | Construct a vector from an angle and magnitude. -- avec :: Floating u => Radian -> u -> Vec2 u-avec theta d = V2 x y where- ang = fromRadian theta- x = d * cos ang- y = d * sin ang-+avec theta d = V2 x y + where+ ang = fromRadian theta+ x = d * cos ang+ y = d * sin ang -- | The vector between two points --@@ -465,7 +394,7 @@ -- from the x-axis. -- direction :: (Floating u, Real u) => Vec2 u -> Radian-direction (V2 x y) = langle (P2 0 0) (P2 x y)+direction (V2 x y) = lineDirection (P2 0 0) (P2 x y) -- | Length of a vector. --@@ -476,8 +405,9 @@ -- vangle :: (Floating u, Real u, InnerSpace (Vec2 u)) => Vec2 u -> Vec2 u -> Radian-vangle u v = realToFrac $ acos $ (u <.> v) / (on (*) magnitude u v)+vangle u v = realToFrac $ acos $ (u <.> v) / (magnitude u * magnitude v) + -------------------------------------------------------------------------------- -- Points @@ -508,12 +438,11 @@ maxPt :: Ord u => Point2 u -> Point2 u -> Point2 u maxPt (P2 x y) (P2 x' y') = P2 (max x x') (max y y') - -- | Calculate the counter-clockwise angle between two points -- and the x-axis. ---langle :: (Floating u, Real u) => Point2 u -> Point2 u -> Radian-langle (P2 x1 y1) (P2 x2 y2) = step (x2 - x1) (y2 - y1)+lineDirection :: (Floating u, Real u) => Point2 u -> Point2 u -> Radian+lineDirection (P2 x1 y1) (P2 x2 y2) = step (x2 - x1) (y2 - y1) where -- north-east quadrant step x y | pve x && pve y = toRadian $ atan (y/x) @@ -530,81 +459,7 @@ pve a = signum a >= 0 - ----------------------------------------------------------------------------------- Frame operations---- | Create a frame with standard (orthonormal bases) at the --- supplied point.----ortho :: Num u => Point2 u -> Frame2 u-ortho o = Frame2 (V2 1 0) (V2 0 1) o---- | Displace the origin of the frame by the supplied vector.----displaceOrigin :: Num u => Vec2 u -> Frame2 u -> Frame2 u-displaceOrigin v (Frame2 e0 e1 o) = Frame2 e0 e1 (o.+^v)---- | \'World coordinate\' calculation of a point in the supplied--- frame.----pointInFrame :: Num u => Point2 u -> Frame2 u -> Point2 u-pointInFrame (P2 x y) (Frame2 vx vy o) = (o .+^ (vx ^* x)) .+^ (vy ^* y) ---- | Concatenate the elements of the frame as columns forming a--- 3x3 matrix. Points and vectors are considered homogeneous --- coordinates - triples where the least element is either 0 --- indicating a vector or 1 indicating a point:------ > Frame (V2 e0x e0y) (V2 e1x e1y) (P2 ox oy)--- --- becomes------ > (M3'3 e0x e1x ox--- > e0y e1y oy--- > 0 0 1 )-----frame2Matrix :: Num u => Frame2 u -> Matrix3'3 u-frame2Matrix (Frame2 (V2 e0x e0y) (V2 e1x e1y) (P2 ox oy)) = - M3'3 e0x e1x ox - e0y e1y oy - 0 0 1----- | Interpret the matrix as columns forming a frame.------ > (M3'3 e0x e1x ox--- > e0y e1y oy--- > 0 0 1 )------ becomes------ > Frame (V2 e0x e0y) (V2 e1x e1y) (P2 ox oy)--- -matrix2Frame :: Matrix3'3 u -> Frame2 u-matrix2Frame (M3'3 e0x e1x ox - e0y e1y oy- _ _ _ ) = Frame2 (V2 e0x e0y) (V2 e1x e1y) (P2 ox oy)----- | /Multiplication/ of frames to form their product.----frameProduct :: (Num u, InnerSpace (Vec2 u)) - => Frame2 u -> Frame2 u -> Frame2 u-frameProduct = matrix2Frame `oo` on (*) frame2Matrix------ | Is the origin at (0,0) and are the basis vectors orthogonal --- with unit length?----standardFrame :: Num u => Frame2 u -> Bool-standardFrame (Frame2 (V2 1 0) (V2 0 1) (P2 0 0)) = True-standardFrame _ = False----------------------------------------------------------------------------------- -- Matrix construction -- | Construct the identity matrix:@@ -743,36 +598,44 @@ ++ -------------------------------------------------------------------------------- -- Radians -- | The epislion used for floating point equality on radians.+-- radian_epsilon :: Double radian_epsilon = 0.0001 -- | Equality on radians, this is the operation used for (==) in -- Radian\'s Eq instance.+-- req :: Radian -> Radian -> Bool req a b = (fromRadian $ abs (a-b)) < radian_epsilon -- | Convert to radians.+-- toRadian :: Real a => a -> Radian toRadian = Radian . realToFrac -- | Convert from radians.+-- fromRadian :: Fractional a => Radian -> a fromRadian = realToFrac . getRadian -- | Degrees to radians.+-- d2r :: (Floating a, Real a) => a -> Radian d2r = Radian . realToFrac . (*) (pi/180) -- | Radians to degrees.+-- r2d :: (Floating a, Real a) => Radian -> a r2d = (*) (180/pi) . fromRadian @@ -785,6 +648,8 @@ i :: Integer dec :: Double (i,dec) = properFraction $ r2d r++ --------------------------------------------------------------------------------
src/Wumpus/Core/GraphicsState.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleInstances #-} {-# OPTIONS -Wall #-} --------------------------------------------------------------------------------@@ -10,8 +8,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more---+-- Portability : GHC -- -- Data types for stroke and label attributes and type classes -- for conversion to PostScript\'s colour and matrix @@ -39,7 +36,6 @@ -- @findfont@ operations in the generated PostScript if -- subsequent elements share the same values. -- --- -- -------------------------------------------------------------------------------- @@ -47,9 +43,10 @@ module Wumpus.Core.GraphicsState ( -- * Data types + GraphicsState(..) -- ** Stroke attributes- StrokeAttr(..)+ , StrokeAttr(..) , LineCap(..) , LineJoin(..) , DashPattern(..)@@ -59,26 +56,32 @@ , FontFace(..) , SVGFontStyle(..) - -- ** Colour- , PSRgb-- -- ** Current Translation Matrix- , CTM(..)-- -- * Convert to CTM- , ToCTM(..)-- -- * Convert to PSColour- , PSColour(..)+ -- ** Default graphic state+ , zeroGS ) where import Wumpus.Core.Colour-import Wumpus.Core.Geometry-import Wumpus.Core.Utils -- Graphics state datatypes +-- | Graphics state used by the rendering monads.+--+-- This type is hidden by the top-level module @Wumpus.Core@.+--+data GraphicsState = GraphicsState+ { gs_draw_colour :: RGBi+ , gs_font_size :: Int+ , gs_font_face :: FontFace+ , gs_line_width :: Double+ , gs_miter_limit :: Double+ , gs_line_cap :: LineCap+ , gs_line_join :: LineJoin+ , gs_dash_pattern :: DashPattern + }+ deriving (Eq,Show)++ -- | Stroke attributes are an algebriac type rather than a -- record type. This is for convenience when attributing paths - -- paths can be attibuted with just the differences from the @@ -94,15 +97,18 @@ -- | Line cap - default in output is butt.+-- data LineCap = CapButt | CapRound | CapSquare deriving (Enum,Eq,Show) -- | Line join - default in output is miter.+-- data LineJoin = JoinMiter | JoinRound | JoinBevel deriving (Enum,Eq,Show) -- | Dash pattern - either a solid line or a list of on-off pairs -- together with an /offset/ into the dashes.+-- data DashPattern = Solid | Dash Int [(Int,Int)] deriving (Eq,Show) @@ -146,96 +152,18 @@ | SVG_OBLIQUE | SVG_BOLD_OBLIQUE deriving (Eq,Ord,Show) -type PSRgb = RGB3 Double ----- | PostScript's current transformation matrix.--- --- PostScript and its documentation considers the matrix to be --- in this form:------ > | a b 0 |--- > | c d 0 | --- > | tx ty 1 |--- --- i.e it considers the homogeneous coordinates of an affine --- frame as /rows/ rather than /columns/ (Wumpus uses rows, as --- they were the usual representation in the geometry --- presentations that inspired it).--- --- Using the component names that we have used in the --- description of 'Frame2', the CTM is:------ > | e0x e0y 0 |--- > | e1x e1y 0 | --- > | ox oy 1 |--- --- The CTM is represented in PostScript as an array, using our --- names its layout is------ > [ e0x e0y e1x e1y ox oy ] ------ Some examples, the scaling matrix:------ > | sx 0 0 |--- > | 0 sy 0 | = [ sx 0 0 sy 0 0 ]--- > | 0 0 1 |--- --- Translation (displacement) :------ > | 1 0 0 |--- > | 0 1 0 | = [ 1 0 0 1 tx ty ]--- > | tx ty 1 |--- --- Rotation:--- --- > | cos(a) sin(a) 0 |--- > | -sin(a) cos(a) 0 | = [ cos(a) sin(a) -sin(a) cos(a) 0 0 ]--- > | 0 0 1 |--data CTM u = CTM !u !u !u !u !u !u- deriving (Eq,Show)--type instance DUnit (CTM u) = u------------------------------------------------------------------------------------- Conversion to CTM---- | Convert to the CTM. Wumpus offshores affine transformations --- to PostScript as @concat@ commands. So frames and matrices --- must support being represented as the CTM.----class ToCTM a where - toCTM :: u ~ DUnit a => a -> CTM u--instance ToCTM (Frame2 a) where- toCTM (Frame2 (V2 e0x e0y) (V2 e1x e1y) (P2 ox oy)) - = CTM e0x e0y e1x e1y ox oy- --instance ToCTM (Matrix3'3 a) where- toCTM (M3'3 e0x e1x ox - e0y e1y oy - _ _ _ ) - = CTM e0x e0y e1x e1y ox oy-------------------------------------------------------------------------------------- Conversion to PSColour---- | Convert to RGB [0,1] for PostScript rendering.-class PSColour a where psColour :: a -> RGB3 Double--instance PSColour (RGB3 Double) where- psColour (RGB3 r g b) = RGB3 (ramp r) (ramp g) (ramp b)--instance PSColour (HSB3 Double) where- psColour = psColour . hsb2rgb--instance PSColour (Gray Double) where- psColour = psColour . gray2rgb---+-- | The initial graphics state +zeroGS :: GraphicsState +zeroGS = GraphicsState { gs_draw_colour = black+ , gs_font_size = (-1)+ , gs_font_face = unmatchable_face+ , gs_line_width = 1+ , gs_miter_limit = 1+ , gs_line_cap = CapButt+ , gs_line_join = JoinMiter+ , gs_dash_pattern = Solid+ }+ where+ unmatchable_face = FontFace "DONT_MATCH" "" SVG_BOLD_OBLIQUE
src/Wumpus/Core/OneList.hs view
@@ -3,7 +3,7 @@ -------------------------------------------------------------------------------- -- | -- Module : Wumpus.Core.OneList--- Copyright : (c) Stephen Tetley 2010+-- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 -- -- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>@@ -12,16 +12,13 @@ -- -- Data type for non-empty lists. -- --- Structurally the same as OneMany - but used for a different--- purpose.--- -------------------------------------------------------------------------------- module Wumpus.Core.OneList ( -- OneMany OneList- , ViewOL(..)+ , ViewL(..) , one , cons@@ -38,7 +35,7 @@ ) where -import Data.Semigroup -- package: algebra+import Data.Semigroup -- package: algebra import Control.Applicative import Data.Foldable@@ -47,14 +44,15 @@ import Prelude hiding ( head ) --- type OneMany a = OneList a data OneList a = One a | Many a (OneList a) deriving (Eq) -data ViewOL a = OneL a | a :<< (OneList a)+data ViewL a = OneL a | a :< (OneList a) deriving (Eq) ++ -------------------------------------------------------------------------------- -- Instances @@ -105,9 +103,9 @@ head (One a) = a head (Many a _) = a -viewl :: OneList a -> ViewOL a+viewl :: OneList a -> ViewL a viewl (One a) = OneL a-viewl (Many a as) = a :<< as+viewl (Many a as) = a :< as -- | Construct Many. Not this function throws a error if the list has -- zero or one elements
src/Wumpus/Core/OutputPostScript.hs view
@@ -1,22 +1,22 @@+{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- |--- Module : Wumpus.Core.OutputPostScript+-- Module : Wumpus.Core.PostScript -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 ----- Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC -- -- Output PostScript - either PostScript (PS) files or -- EPS (Encapusulated PostScript) files can be generated. ----- -------------------------------------------------------------------------------- -module Wumpus.Core.OutputPostScript +module Wumpus.Core.OutputPostScript ( -- * Output PostScript writePS@@ -29,22 +29,118 @@ import Wumpus.Core.BoundingBox import Wumpus.Core.Colour+import Wumpus.Core.FormatCombinators import Wumpus.Core.Geometry import Wumpus.Core.GraphicsState+import Wumpus.Core.OneList import Wumpus.Core.PictureInternal-import Wumpus.Core.PostScript+import Wumpus.Core.PostScriptDoc import Wumpus.Core.TextEncoder-import Wumpus.Core.TextEncodingInternal+import Wumpus.Core.TextInternal import Wumpus.Core.TextLatin1 import Wumpus.Core.Utils +import Control.Applicative hiding ( empty, some )+import Control.Monad -import MonadLib hiding ( Label )+import qualified Data.Foldable as F+import Data.Maybe +import Data.Time -import qualified Data.Foldable as F +--------------------------------------------------------------------------------+-- PsMonad +-- PsMonad is at least a Reader and a State...+--+-- Graphics state works differently to SVG - PostScript has no +-- nesting (at least not in the code Wumpus generates) so there+-- are no /savings/ by diffing from an outer environment, instead+-- the diff is with the last drawn object.+--+newtype PsMonad a = PsMonad { + getPsMonad :: TextEncoder -> GraphicsState -> (a,GraphicsState) } ++instance Functor PsMonad where+ fmap f mf = PsMonad $ \r s -> let (a,s1) = getPsMonad mf r s in (f a,s1)++instance Applicative PsMonad where+ pure a = PsMonad $ \_ s -> (a,s)+ mf <*> ma = PsMonad $ \r s -> let (f,s1) = getPsMonad mf r s+ (a,s2) = getPsMonad ma r s1+ in (f a,s2)++instance Monad PsMonad where+ return a = PsMonad $ \_ s -> (a,s)+ m >>= k = PsMonad $ \r s -> let (a,s1) = getPsMonad m r s+ in (getPsMonad . k) a r s1+ ++runPsMonad :: TextEncoder -> PsMonad a -> a+runPsMonad enc mf = fst $ getPsMonad mf enc zeroGS++askCharCode :: Int -> PsMonad (Either GlyphName GlyphName)+askCharCode i = PsMonad $ \r s -> case lookupByCharCode i r of+ Just n -> (Right n,s)+ Nothing -> (Left $ ps_fallback r,s)++runLocalGS :: GSUpdate -> PsMonad a -> PsMonad a+runLocalGS upd mf = + PsMonad $ \r s -> let (a,_) = getPsMonad mf r (getGSU upd s) in (a,s)++getDrawColour :: PsMonad RGBi+getDrawColour = PsMonad $ \_ s -> (gs_draw_colour s, s)++setDrawColour :: RGBi -> PsMonad ()+setDrawColour a = PsMonad $ \_ s -> ((), s {gs_draw_colour=a})+++getFontAttr :: PsMonad FontAttr+getFontAttr = PsMonad $ \_ s -> let sz = gs_font_size s + ff = gs_font_face s+ in (FontAttr sz ff, s)++setFontAttr :: FontAttr -> PsMonad ()+setFontAttr (FontAttr sz ff) = + PsMonad $ \_ s -> ((), s { gs_font_size=sz,gs_font_face=ff })++ +getLineWidth :: PsMonad Double+getLineWidth = PsMonad $ \_ s -> (gs_line_width s, s)++setLineWidth :: Double -> PsMonad ()+setLineWidth a = PsMonad $ \_ s -> ((), s { gs_line_width=a })+++getMiterLimit :: PsMonad Double+getMiterLimit = PsMonad $ \_ s -> (gs_miter_limit s,s)++setMiterLimit :: Double -> PsMonad ()+setMiterLimit a = PsMonad $ \_ s -> ((), s { gs_miter_limit=a })+++getLineCap :: PsMonad LineCap+getLineCap = PsMonad $ \_ s -> (gs_line_cap s,s)++setLineCap :: LineCap -> PsMonad ()+setLineCap a = PsMonad $ \_ s -> ((), s { gs_line_cap=a })+++getLineJoin :: PsMonad LineJoin+getLineJoin = PsMonad $ \_ s -> (gs_line_join s,s)++setLineJoin :: LineJoin -> PsMonad ()+setLineJoin a = PsMonad $ \_ s -> ((), s { gs_line_join=a })+++getDashPattern :: PsMonad DashPattern+getDashPattern = PsMonad $ \_ s -> (gs_dash_pattern s,s)++setDashPattern :: DashPattern -> PsMonad ()+setDashPattern a = PsMonad $ \_ s -> ((), s { gs_dash_pattern=a })++ -------------------------------------------------------------------------------- -- Render to PostScript @@ -53,9 +149,8 @@ -- writePS :: (Real u, Floating u, PSUnit u) => FilePath -> TextEncoder -> [Picture u] -> IO ()-writePS filepath enc pic = do - timestamp <- mkTimeStamp- writeFile filepath $ psDraw timestamp enc pic+writePS filepath enc pic = + getZonedTime >>= \ztim -> writeFile filepath (show $ psDraw ztim enc pic) -- | Output a picture to an EPS (Encapsulated PostScript) file. -- The .eps file can then be imported or embedded in another @@ -63,301 +158,348 @@ -- writeEPS :: (Real u, Floating u, PSUnit u) => FilePath -> TextEncoder -> Picture u -> IO ()-writeEPS filepath enc pic = do- timestamp <- mkTimeStamp- writeFile filepath $ epsDraw timestamp enc pic+writeEPS filepath enc pic =+ getZonedTime >>= \ztim -> writeFile filepath (show $ epsDraw ztim enc pic) --- | Version of 'writePS' - using Latin1 encoding. +-- | Version of 'writePS' - using Latin1 encoding.+-- writePS_latin1 :: (Real u, Floating u, PSUnit u) - => FilePath -> [Picture u] -> IO ()-writePS_latin1 filepath = writePS filepath latin1Encoder + => FilePath -> [Picture u] -> IO ()+writePS_latin1 filepath = writePS filepath latin1Encoder -- | Version of 'writeEPS' - using Latin1 encoding. +-- writeEPS_latin1 :: (Real u, Floating u, PSUnit u) - => FilePath -> Picture u -> IO ()+ => FilePath -> Picture u -> IO () writeEPS_latin1 filepath = writeEPS filepath latin1Encoder -- -------------------------------------------------------------------------------- -- Internals -- | Draw a picture, generating PostScript output.+--+-- Note - the bounding box may /below the origin/ - if it is, it+-- will need translating.+--+ psDraw :: (Real u, Floating u, PSUnit u) - => String -> TextEncoder -> [Picture u] -> PostScript-psDraw timestamp enc pics = runWumpus enc $ do- psHeader 1 timestamp- zipWithM_ psDrawPage pages pics- psFooter+ => ZonedTime -> TextEncoder -> [Picture u] -> Doc+psDraw timestamp enc pics = + let body = vcat $ runPsMonad enc $ zipWithM psDrawPage pages pics+ in vcat [ psHeader 1 timestamp+ , body+ , psFooter + ] where pages = map (\i -> (show i,i)) [1..] --psDrawPage :: (Real u, Floating u, PSUnit u) - => (String,Int) -> Picture u -> WumpusM ()-psDrawPage (lbl,ordinal) pic = do- dsc_Page lbl ordinal- ps_gsave- cmdtrans- outputPicture pic- ps_grestore- ps_showpage- where- (_,mbv) = repositionProperties pic- cmdtrans = maybe (return ()) (\(V2 x y) -> ps_translate x y) mbv- +-- | Note the bounding box may /below the origin/ - if it is, it+-- will need translating.+--+psDrawPage :: (Real u, Floating u, PSUnit u)+ => (String,Int) -> Picture u -> PsMonad Doc+psDrawPage (lbl,ordinal) pic = + let (_,cmdtrans) = imageTranslation pic in + (\doc -> vcat [ dsc_Page lbl ordinal+ , ps_gsave+ , cmdtrans+ , doc+ , ps_grestore+ , ps_showpage+ ]) + <$> picture pic --- | Note the bounding box may /below the origin/ - if it is, it +-- | Note the bounding box may /below the origin/ - if it is, it -- will need translating. ---epsDraw :: (Real u, Floating u, PSUnit u) - => String -> TextEncoder -> Picture u -> PostScript-epsDraw timestamp enc pic = runWumpus enc $ do - epsHeader bb timestamp - ps_gsave- cmdtrans- outputPicture pic- ps_grestore- epsFooter - where- (bb,mbv) = repositionProperties pic- cmdtrans = maybe (return ()) (\(V2 x y) -> ps_translate x y) mbv- +epsDraw :: (Real u, Floating u, PSUnit u)+ => ZonedTime -> TextEncoder -> Picture u -> Doc+epsDraw timestamp enc pic =+ let (bb,cmdtrans) = imageTranslation pic + body = runPsMonad enc (picture pic) + in vcat [ epsHeader bb timestamp+ , ps_gsave+ , cmdtrans+ , body+ , ps_grestore+ , epsFooter+ ] -psHeader :: Int -> String -> WumpusM ()-psHeader pagecount timestamp = do- bang_PS- dsc_Pages pagecount- dsc_CreationDate $ parens timestamp- dsc_EndComments +imageTranslation :: (Ord u, PSUnit u) => Picture u -> (BoundingBox u, Doc)+imageTranslation pic = case repositionDeltas pic of+ (bb, Nothing) -> (bb, empty)+ (bb, Just v) -> (bb, ps_translate v) -epsHeader :: PSUnit u => BoundingBox u -> String -> WumpusM ()-epsHeader bb timestamp = do- bang_EPS- dsc_BoundingBox llx lly urx ury- dsc_CreationDate $ parens timestamp- dsc_EndComments++--------------------------------------------------------------------------------+++picture :: (Real u, Floating u, PSUnit u) => Picture u -> PsMonad Doc+picture (Leaf (_,xs) ones) = bracketTrafos xs $ revConcat primitive ones+picture (Picture (_,xs) ones) = bracketTrafos xs $ revConcat picture ones+picture (Clip (_,xs) cp pic) = bracketTrafos xs $+ (vconcat <$> clipPath cp <*> picture pic)+picture (Group (_,xs) fn pic) = bracketTrafos xs (runLocalGS fn (picture pic))+++revConcat :: (a -> PsMonad Doc) -> OneList a -> PsMonad Doc+revConcat fn ones = some empty <$> F.foldrM step None ones where- (llx,lly,urx,ury) = getBounds bb+ step e ac = (\d -> d `conc` ac) <$> fn e+ conc d None = Some d+ conc d (Some ac) = Some $ ac `vconcat` d -getBounds :: Num u => BoundingBox u -> (u,u,u,u)-getBounds (BBox (P2 llx lly) (P2 urx ury)) = (llx,lly,urx,ury) -psFooter :: WumpusM ()-psFooter = dsc_EOF+primitive :: (Real u, Floating u, PSUnit u) => Primitive u -> PsMonad Doc+primitive (PPath props _ pp) = primPath props pp+primitive (PLabel props _ lbl) = primLabel props lbl+primitive (PEllipse props _ ell) = primEllipse props ell -epsFooter :: WumpusM ()-epsFooter = do- ps_showpage- dsc_EOF+primPath :: PSUnit u+ => PathProps -> PrimPath u -> PsMonad Doc+primPath (CFill rgb) p = + (\rgbd -> vcat [rgbd, makeStartPath p, ps_closepath, ps_fill]) + <$> deltaDrawColour rgb --- Create margins at the left and bottom of 4 points...+primPath (CStroke attrs rgb) p = + (\rgbd attrd -> vcat [ rgbd, attrd, makeStartPath p+ , ps_closepath, ps_stroke ])+ <$> deltaDrawColour rgb <*> deltaStrokeAttrs attrs+ +primPath (OStroke attrs rgb) p = + (\rgbd attrd -> vcat [rgbd, attrd, makeStartPath p, ps_stroke]) + <$> deltaDrawColour rgb <*> deltaStrokeAttrs attrs +primPath (CFillStroke fc attrs sc) p = + (\d1 d2 -> vcat [d1,d2])+ <$> primPath (CFill fc) p <*> primPath (CStroke attrs sc) p --- | outputPicture --- Frame changes, representing scalings translation, rotations...--- are drawn when they are encountered as a @concat@ statement in a --- block of @gsave ... grestore@.----outputPicture :: (Real u, Floating u, PSUnit u) => Picture u -> WumpusM ()-outputPicture (PicBlank _) = return ()-outputPicture (Single (fr,_) prim) = - updateFrame fr $ outputPrimitive prim-outputPicture (Picture (fr,_) ones) =- updateFrame fr $ F.foldrM (\p _ -> outputPicture p) () ones-outputPicture (Clip (fr,_) cp p) = - updateFrame fr $ do { clipPath cp ; outputPicture p } +clipPath :: PSUnit u => PrimPath u -> PsMonad Doc+clipPath p = pure $ vcat [makeStartPath p , ps_closepath, ps_clip] --- | @updateFrame@ relies on the current frame, when translated--- to a matrix being invertible.++makeStartPath :: PSUnit u => PrimPath u -> Doc+makeStartPath (PrimPath start xs) = + vcat $ ps_newpath : ps_moveto start : map makePathSegment xs++++makePathSegment :: PSUnit u => PrimPathSegment u -> Doc+makePathSegment (PLineTo p1) = ps_lineto p1 +makePathSegment (PCurveTo p1 p2 p3) = ps_curveto p1 p2 p3 +++-- | Drawing stroked ellipse has an unfortunate - but (probably) +-- unavoidable deficiency. ----- This is an allowable optimization because the current frame--- is only manipulated with the affine transformations (scalings, --- rotations...) which are invertible. --- --- It also performs another optimization:+-- The use of PostScript\'s @concat@ operator to alter the arc +-- hw/hh will vary the line width during the drawing of a stroked +-- ellipse. ----- If the frame is the standard frame @ [1 0 0 1 0 0] @ then --- the monadic action is run as-is rather than being nested--- in a block:--- --- > [1 0 0 1 0 0] concat--- > ...--- > [1 0 0 1 0 0] concat+-- For good stroked ellipses, Bezier curves constructed from +-- PrimPaths should be used. --+primEllipse :: (Real u, Floating u, PSUnit u) + => EllipseProps -> PrimEllipse u -> PsMonad Doc+primEllipse props (PrimEllipse center hw hh ctm) =+ bracketPrimCTM center (scaleCTM 1 (hh/hw) ctm) (drawF props)+ where+ drawF (EFill rgb) pt = fillArcPath rgb hw pt+ drawF (EStroke attrs rgb) pt = strokeArcPath rgb attrs hw pt+ drawF (EFillStroke fc attrs sc) pt = + vconcat <$> fillArcPath fc hw pt <*> strokeArcPath sc attrs hw pt+ -updateFrame :: (Fractional u, PSUnit u) => Frame2 u -> WumpusM () -> WumpusM ()-updateFrame frm ma - | standardFrame frm = ma- | otherwise = let m1 = frame2Matrix frm in - do { ps_concat $ toCTM m1- ; ma - ; ps_concat $ toCTM $ invert m1- } -outputPrimitive :: (Real u, Floating u, PSUnit u) => Primitive u -> WumpusM ()-outputPrimitive (PPath (c,dp) p) = outputPath dp c p -outputPrimitive (PLabel props l) = updateFont props $ outputLabel l-outputPrimitive (PEllipse (c,dp) e) = outputEllipse dp c e+-- This will need to become monadic to handle /colour delta/.+--+fillArcPath :: PSUnit u => RGBi -> u -> Point2 u -> PsMonad Doc+fillArcPath rgb radius pt = + (\rgbd -> vcat [ rgbd+ , ps_newpath+ , ps_arc pt radius 0 360+ , ps_closepath+ , ps_fill ])+ <$> deltaDrawColour rgb -updateFont :: LabelProps -> WumpusM () -> WumpusM ()-updateFont (c,fnt) ma = updateColour c $ do - mb_fnt <- deltaFontAttr fnt- maybe (return ()) fontCommand mb_fnt- ma- +strokeArcPath :: PSUnit u + => RGBi -> [StrokeAttr] -> u -> Point2 u -> PsMonad Doc+strokeArcPath rgb attrs radius pt =+ (\rgbd attrd -> vcat [ rgbd+ , attrd+ , ps_newpath+ , ps_arc pt radius 0 360+ , ps_closepath+ , ps_stroke ])+ <$> deltaDrawColour rgb <*> deltaStrokeAttrs attrs -updateColour :: PSColour c => c -> WumpusM () -> WumpusM ()-updateColour c ma = let rgbc = psColour c in do - mb_col <- deltaRgbColour rgbc- maybe (return ()) colourCommand mb_col- ma+-- Note - for the otherwise case, the x-and-y coordinates are +-- encoded in the matrix, hence the @ 0 0 moveto @.+--+primLabel :: (Real u, Floating u, PSUnit u) + => LabelProps -> PrimLabel u -> PsMonad Doc+primLabel (LabelProps rgb font) (PrimLabel basept txt ctm) = + bracketPrimCTM basept ctm mf where- colourCommand :: DRGB -> WumpusM ()- colourCommand (RGB3 r g b) = ps_setrgbcolor r g b- - + mf pt = (\rgbd fontd showd -> vcat [ rgbd, fontd, ps_moveto pt, showd ]) + <$> deltaDrawColour rgb <*> deltaFontAttrs font + <*> encodedText txt +encodedText :: EncodedText -> PsMonad Doc +encodedText etext = vcat <$> (mapM textChunk $ getEncodedText etext) -fontCommand :: FontAttr -> WumpusM ()-fontCommand (FontAttr sz face) = do- ps_findfont (font_name face)- ps_scalefont sz- ps_setfont +textChunk :: TextChunk -> PsMonad Doc+textChunk (SText s) = pure (ps_show $ escapeSpecial s)+textChunk (EscStr s) = pure (ps_glyphshow s)+textChunk (EscInt i) = (either failk ps_glyphshow) <$> askCharCode i + where+ failk gly_name = missingCharCode i gly_name +--------------------------------------------------------------------------------+-- Stroke, font and drawing colour attribute delta - -outputPath :: (PSColour c, PSUnit u) - => DrawPath -> c -> Path u -> WumpusM ()-outputPath CFill c p = updateColour c $ do - startPath p- ps_closepath- ps_fill+-- This needs more thought than SVG as there is no natural +-- nesting to benefit from... -outputPath (CStroke xs) c p = updatePen c xs $ do- startPath p- ps_closepath- ps_stroke+-- All graphics are annotated with colour - the graphics state+-- doesn\'t tell us the actual colour of anything, only if we +-- need to write a colour change to the output.+-- +-- So we compare the current colour with the state - if it is+-- different we put the new colour in the state and output a +-- @setrgbcolor@ message.+--+--+-- Note - because these combinators return /empty/ for no +-- difference some extraneous blank lines are produced in the +-- output.+-- -outputPath (OStroke xs) c p = updatePen c xs $ do- startPath p- ps_stroke- +deltaDrawColour :: RGBi -> PsMonad Doc+deltaDrawColour rgb = getDrawColour >>= \inh -> + if rgb==inh then return empty+ else setDrawColour rgb >> return (ps_setrgbcolor rgb) -startPath :: PSUnit u => Path u -> WumpusM ()-startPath (Path (P2 x y) xs) = do- ps_newpath- ps_moveto x y- mapM_ outputPathSeg xs +-- Note - the current way of handling Stroke Deltas for +-- PostScript uses an awful lot of operations. +-- +-- The GraphicsState data types need some more thought +-- +data StrokeDelta = StrokeDelta+ { sd_line_width :: Double+ , sd_miter_limit :: Double+ , sd_line_cap :: LineCap+ , sd_line_join :: LineJoin+ , sd_dash_pattern :: DashPattern+ } -clipPath :: PSUnit u => Path u -> WumpusM ()-clipPath p = do - startPath p- ps_closepath- ps_clip+-- | Note - the values here must be in- sync with zeroGS.+--+zeroSD :: StrokeDelta+zeroSD = StrokeDelta { sd_line_width = 1+ , sd_miter_limit = 1+ , sd_line_cap = CapButt+ , sd_line_join = JoinMiter+ , sd_dash_pattern = Solid+ } +makeStrokeDelta :: [StrokeAttr] -> StrokeDelta+makeStrokeDelta = foldr fn zeroSD+ where+ fn (LineWidth a) sd = sd { sd_line_width = a }+ fn (MiterLimit a) sd = sd { sd_miter_limit = a }+ fn (LineCap a) sd = sd { sd_line_cap = a }+ fn (LineJoin a) sd = sd { sd_line_join = a }+ fn (DashPattern a) sd = sd { sd_dash_pattern = a} -updatePen :: PSColour c => c -> [StrokeAttr] -> WumpusM () -> WumpusM ()-updatePen c xs ma = let (mset, mreset) = strokeSetReset xs in - updateColour c $ do { mset ; ma ; mreset } -strokeSetReset :: [StrokeAttr] -> (WumpusM (), WumpusM ())-strokeSetReset = foldr (appro link cmd id) (return (), return ())+deltaStrokeAttrs :: [StrokeAttr] -> PsMonad Doc+deltaStrokeAttrs xs = step $ makeStrokeDelta xs where- link Nothing funs = funs - link (Just (f,g)) (fs,gs) = (fs >> f, gs >> g)- - mkSetReset mf = maybe Nothing (\(a,b) -> Just (mf a, mf b))- - cmd (LineWidth d) = mkSetReset ps_setlinewidth $ deltaStrokeWidth d- cmd (MiterLimit d) = mkSetReset ps_setmiterlimit $ deltaMiterLimit d- cmd (LineCap lc) = mkSetReset ps_setlinecap $ deltaLineCap lc- cmd (LineJoin lj) = mkSetReset ps_setlinejoin $ deltaLineJoin lj- cmd (DashPattern dp) = mkSetReset ps_setdash $ deltaDashPattern dp+ step sd = (\d1 d2 d3 d4 d5 -> hcat $ catMaybes [d1,d2,d3,d4,d5]) + <$> lw sd <*> ml sd <*> lc sd <*> lj sd <*> dp sd + lw sd = let d = sd_line_width sd in + getLineWidth >>= \inh -> + if d == inh + then return Nothing + else setLineWidth d >> return (Just $ ps_setlinewidth d) -outputPathSeg :: PSUnit u => PathSegment u -> WumpusM ()-outputPathSeg (PLineTo (P2 x y)) = ps_lineto x y-outputPathSeg (PCurveTo p1 p2 p3) = ps_curveto x1 y1 x2 y2 x3 y3 - where- P2 x1 y1 = p1- P2 x2 y2 = p2- P2 x3 y3 = p3+ ml sd = let d = sd_miter_limit sd in + getMiterLimit >>= \inh -> + if d==inh + then return Nothing + else setMiterLimit d >> return (Just $ ps_setmiterlimit d)+ + lc sd = let d = sd_line_cap sd in+ getLineCap >>= \inh -> + if d==inh + then return Nothing+ else setLineCap d >> return (Just $ ps_setlinecap d)+ + lj sd = let d = sd_line_join sd in + getLineJoin >>= \inh -> + if d==inh + then return Nothing+ else setLineJoin d >> return (Just $ ps_setlinejoin d) + dp sd = let d = sd_dash_pattern sd in+ getDashPattern >>= \inh -> + if d==inh + then return Nothing+ else setDashPattern d >> return (Just $ ps_setdash d) --- | This is not very good - the use of PostScript's @concat@ --- operator will vary the line width during the drawing of a --- stroked ellipse.----outputEllipse :: (PSColour c, Real u, Floating u, PSUnit u)- => DrawEllipse -> c -> PrimEllipse u -> WumpusM ()-outputEllipse dp c (PrimEllipse pt@(P2 x y) hw hh ctm)- | hw==hh && ctm == identityCTM = outputArc dp c x y hw- | otherwise = - let matrix = matrixRepCTM $ scaleCTM 1 (hh/hw) ctm- matrix' = invert matrix- (P2 dx dy) = matrix' *# pt - in do { ps_concat $ toCTM matrix- ; outputArc dp c dx dy hw- ; ps_concat $ toCTM $ matrix'- } -outputArc :: (PSColour c, PSUnit u) - => DrawEllipse -> c -> u -> u -> u -> WumpusM ()-outputArc EFill c x y r = updateColour c $ do - ps_newpath- ps_arc x y r 0 360 - ps_closepath- ps_fill -outputArc (EStroke xs) c x y r = updatePen c xs $ do - ps_newpath- ps_arc x y r 0 360 - ps_closepath- ps_stroke+deltaFontAttrs :: FontAttr -> PsMonad Doc+deltaFontAttrs fa = getFontAttr >>= \inh ->+ if fa==inh then return empty + else setFontAttr fa >> return (makeFontAttrs fa) +makeFontAttrs :: FontAttr -> Doc+makeFontAttrs (FontAttr sz face) = + vcat [ ps_findfont (font_name face), ps_scalefont sz, ps_setfont ] --- Note - for the otherwise case the x-and-y coordinates are --- encoded in the matrix, hence the @ 0 0 moveto @.----outputLabel :: (Real u, Floating u, PSUnit u) => Label u -> WumpusM ()-outputLabel (Label (P2 x y) entxt ctm) - | ctm == identityCTM = do { ps_moveto x y; outputEncodedText entxt }- | otherwise = do { ps_concat $ toCTM matrix- ; ps_moveto 0 (0 :: Double)- ; outputEncodedText entxt- ; ps_concat $ toCTM $ invert matrix- }- where- matrix = translMatrixRepCTM x y ctm -outputEncodedText :: EncodedText -> WumpusM () -outputEncodedText = mapM_ outputTextChunk . getEncodedText+--------------------------------------------------------------------------------+-- Bracket matrix and PrimCTM trafos -outputTextChunk :: TextChunk -> WumpusM () -outputTextChunk (SText s) = ps_show $ escapeStringPS s+bracketTrafos :: (Real u, Floating u, PSUnit u) + => [AffineTrafo u] -> PsMonad Doc -> PsMonad Doc+bracketTrafos xs ma = bracketMatrix (concatTrafos xs) ma -outputTextChunk (EscInt i) = - ask >>= \env -> maybe (failk env) ps_glyphshow $ lookupByCharCode i env+bracketMatrix :: (Fractional u, PSUnit u) + => Matrix3'3 u -> PsMonad Doc -> PsMonad Doc+bracketMatrix mtrx ma + | mtrx == identityMatrix = ma+ | otherwise = (\doc -> vcat [inn, doc, out]) <$> ma where- failk = missingCode i . ps_fallback + inn = ps_concat $ mtrx+ out = ps_concat $ invert mtrx -outputTextChunk (EscStr s) = ps_glyphshow s -missingCode :: CharCode -> GlyphName -> WumpusM ()-missingCode i fallback = do- ps_comment $ "missing lookup for &#" ++ show i ++ ";" - ps_glyphshow fallback- +bracketPrimCTM :: forall u. (Real u, Floating u, PSUnit u)+ => Point2 u -> PrimCTM u + -> (Point2 u -> PsMonad Doc) -> PsMonad Doc+bracketPrimCTM pt@(P2 x y) ctm mf + | ctm == identityCTM = mf pt+ | otherwise = (\doc -> vcat [inn, doc, out]) <$> mf zeroPt'+ where+ zeroPt' :: Point2 u+ zeroPt' = zeroPt++ mtrx = translMatrixRepCTM x y ctm+ inn = ps_concat $ mtrx+ out = ps_concat $ invert mtrx
src/Wumpus/Core/OutputSVG.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS -Wall #-} --------------------------------------------------------------------------------@@ -7,9 +7,9 @@ -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 ----- Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC -- -- Output SVG. --@@ -30,42 +30,96 @@ -- -------------------------------------------------------------------------------- -module Wumpus.Core.OutputSVG- ( - +module Wumpus.Core.OutputSVG + (+ -- * Output SVG writeSVG , writeSVG_latin1- + ) where -import Wumpus.Core.AffineTrans+import Wumpus.Core.BoundingBox+import Wumpus.Core.Colour+import Wumpus.Core.FormatCombinators import Wumpus.Core.Geometry import Wumpus.Core.GraphicsState+import Wumpus.Core.OneList+import Wumpus.Core.PageTranslation import Wumpus.Core.PictureInternal-import Wumpus.Core.SVG+import Wumpus.Core.SVGDoc import Wumpus.Core.TextEncoder-import Wumpus.Core.TextEncodingInternal+import Wumpus.Core.TextInternal import Wumpus.Core.TextLatin1 import Wumpus.Core.Utils +import Control.Applicative hiding ( empty, some )+import qualified Data.Foldable as F -import MonadLib hiding ( Label )+-- SvgMonad is two Readers plus Int state for clip paths...+--+newtype SvgMonad a = SvgMonad { + getSvgMonad :: TextEncoder -> GraphicsState -> Int -> (a,Int) } -import Text.XML.Light -import qualified Data.Foldable as F -type Clipped = Bool+instance Functor SvgMonad where+ fmap f mf = SvgMonad $ \r1 r2 s -> let (a,s1) = getSvgMonad mf r1 r2 s+ in (f a,s1) +instance Applicative SvgMonad where+ pure a = SvgMonad $ \_ _ s -> (a,s)+ mf <*> ma = SvgMonad $ \r1 r2 s -> let (f,s1) = getSvgMonad mf r1 r2 s+ (a,s2) = getSvgMonad ma r1 r2 s1+ in (f a, s2) -coordChange :: (Num u, Scale t, u ~ DUnit t) => t -> t-coordChange = scale 1 (-1)+instance Monad SvgMonad where+ return a = SvgMonad $ \_ _ s -> (a,s)+ m >>= k = SvgMonad $ \r1 r2 s -> let (a,s1) = getSvgMonad m r1 r2 s+ in (getSvgMonad . k) a r1 r2 s1+ -svg_reflection_matrix :: Num u => Matrix3'3 u-svg_reflection_matrix = scalingMatrix 1 (-1) +runSvgMonad :: TextEncoder -> SvgMonad a -> a+runSvgMonad enc mf = fst $ getSvgMonad mf enc zeroGS 0++newClipLabel :: SvgMonad String+newClipLabel = SvgMonad $ \_ _ s -> ('c':'l':'i':'p':show s, s+1)++askGlyphName :: String -> SvgMonad (Either GlyphName GlyphName)+askGlyphName nm = SvgMonad $ \r1 _ s -> case lookupByGlyphName nm r1 of+ Just a -> (Right $ escapeSpecial a, s)+ Nothing -> (Left $ escapeSpecial $ svg_fallback r1, s)++-- This is different to the PsMonad version, as SVG is nested +-- (and /graphics state/ is via a Reader), so it is the same as +-- local with a Reader monad.+--+runLocalGS :: GSUpdate -> SvgMonad a -> SvgMonad a+runLocalGS upd mf = + SvgMonad $ \r1 r2 s -> getSvgMonad mf r1 (getGSU upd r2) s+++askFontAttr :: SvgMonad FontAttr+askFontAttr = + SvgMonad $ \_ r2 s -> (FontAttr (gs_font_size r2) (gs_font_face r2), s)++askLineWidth :: SvgMonad Double+askLineWidth = SvgMonad $ \_ r2 s -> (gs_line_width r2, s)++askMiterLimit :: SvgMonad Double+askMiterLimit = SvgMonad $ \_ r2 s -> (gs_miter_limit r2, s)++askLineCap :: SvgMonad LineCap+askLineCap = SvgMonad $ \_ r2 s -> (gs_line_cap r2, s)++askLineJoin :: SvgMonad LineJoin+askLineJoin = SvgMonad $ \_ r2 s -> (gs_line_join r2, s)++askDashPattern :: SvgMonad DashPattern+askDashPattern = SvgMonad $ \_ r2 s -> (gs_dash_pattern r2, s)+ -------------------------------------------------------------------------------- -- | Output a picture to a SVG file. @@ -73,286 +127,258 @@ writeSVG :: (Real u, Floating u, PSUnit u) => FilePath -> TextEncoder -> Picture u -> IO () writeSVG filepath enc pic = - writeFile filepath $ unlines $ map ppContent $ svgDraw enc pic + writeFile filepath $ show $ svgDraw enc pic -- | Version of 'writeSVG' - using Latin1 encoding. -- writeSVG_latin1 :: (Real u, Floating u, PSUnit u) => FilePath -> Picture u -> IO ()-writeSVG_latin1 filepath = writeSVG filepath latin1Encoder --+writeSVG_latin1 filepath = writeSVG filepath latin1Encoder svgDraw :: (Real u, Floating u, PSUnit u) - => TextEncoder -> Picture u -> [Content]-svgDraw enc pic = runSVG enc $ do- elem1 <- picture False pic'- prefixXmlDecls (topLevelPic mbvec elem1)- where- pic' = coordChange pic- (_,mbvec) = repositionProperties pic'+ => TextEncoder -> Picture u -> Doc+svgDraw enc original_pic = + let pic = trivialTranslation original_pic+ (_,imgTrafo) = imageTranslation pic+ body = runSvgMonad enc $ picture pic+ in vcat [ xml_version, doctype, elem_svg $ imgTrafo body ] -prefixXmlDecls :: Element -> SvgM [Content]-prefixXmlDecls e = do - enc <- asks svg_encoding_name- let xmlv = xmlVersion enc- return $ [Text xmlv, Text svgDocType, Elem e] -topLevelPic :: PSUnit u => Maybe (Vec2 u) -> Element -> Element-topLevelPic Nothing p = svgElement [p]-topLevelPic (Just (V2 x y)) p = svgElement [gElement [trans_attr] [p]] - where - trans_attr = attr_transform $ val_translate x y+imageTranslation :: (Ord u, PSUnit u) + => Picture u -> (BoundingBox u, Doc -> Doc)+imageTranslation pic = case repositionDeltas pic of+ (bb, Nothing) -> (bb, id)+ (bb, Just v) -> let attr = attr_transform (val_translate v) + in (bb, elem_g attr) +-------------------------------------------------------------------------------- +-- Note - it will be wise to make coordinate remapping and output+-- separate passes (unlike in Wumpus-Core). Then I\'ll at least +-- be able to debug the remapped Picture.+-- -picture :: (Real u, Floating u, PSUnit u) - => Clipped -> Picture u -> SvgM Element-picture _ (PicBlank _) = return $ gElement [] []-picture c (Single (fr,_) prim) = do - elt <- primitive c prim- return $ gElement (maybe [] return $ frameChange fr) [elt] -picture c (Picture (fr,_) ones) = do- -- Note - list in zorder, so we want to draw the tail first - es <- liftM toListH $ F.foldrM fn emptyH ones- return $ gElement (maybe [] return $ frameChange fr) es- where- fn e hl = picture c e >>= \a -> return $ hl `snocH` a- -picture _ (Clip (fr,_) p a) = do - cp <- clipPath p- e1 <- picture True a- return $ gElement (maybe [] return $ frameChange fr) [cp,e1] +picture :: (Real u, Floating u, PSUnit u) => Picture u -> SvgMonad Doc+picture (Leaf (_,xs) ones) = bracketTrafos xs $ revConcat primitive ones+picture (Picture (_,xs) ones) = bracketTrafos xs $ revConcat picture ones+picture (Clip (_,xs) cp pic) = + bracketTrafos xs $ do { lbl <- newClipLabel+ ; d1 <- clipPath lbl cp+ ; d2 <- picture pic+ ; return (vconcat d1 (elem_g (attr_clip_path lbl) d2))+ } +picture (Group (_,xs) fn pic) = bracketTrafos xs (runLocalGS fn (picture pic)) -primitive :: (Real u, Floating u, PSUnit u) - => Clipped -> Primitive u -> SvgM Element-primitive c (PPath props p) = clipAttrib c $ path props p-primitive c (PLabel props l) = clipAttrib c $ label props l-primitive c (PEllipse props e) = clipAttrib c $ ellipse props e +-- This starts with an empty line...+-- --- All clipping paths are closed.-clipPath :: PSUnit u => Path u -> SvgM Element-clipPath p = do- name <- newClipLabel- return $ element_clippath ps `rap` add_attr (attr_id name)+revConcat :: (a -> SvgMonad Doc) -> OneList a -> SvgMonad Doc+revConcat fn ones = some empty <$> F.foldrM step None ones where- ps = closePath $ pathInstructions p+ step e ac = (\d -> d `conc` ac) <$> fn e+ conc d None = Some d+ conc d (Some ac) = Some $ ac `vconcat` d -clipAttrib :: Clipped -> SvgM Element -> SvgM Element-clipAttrib False melt = melt-clipAttrib True melt = do - s <- currentClipLabel- elt <- melt- return $ add_attr (attr_clippath s) elt+primitive :: (Real u, Floating u, PSUnit u) => Primitive u -> SvgMonad Doc+primitive (PPath props xl pp) = drawXLink xl <$> primPath props pp+primitive (PLabel props xl lbl) = drawXLink xl <$> primLabel props lbl+primitive (PEllipse props xl ell) = drawXLink xl <$> primEllipse props ell+ +drawXLink :: XLink -> Doc -> Doc+drawXLink NoLink doc = doc+drawXLink (XLinkHRef href) doc = elem_a_xlink href doc --- None of the remaining translation functions need to be in the--- SvgM monad.+clipPath :: PSUnit u => String -> PrimPath u -> SvgMonad Doc+clipPath clip_id pp = (\doc -> elem_clipPath (attr_id clip_id) doc) <$> path pp -path :: PSUnit u => PathProps -> Path u -> SvgM Element-path (c,dp) p = - return $ element_path ps `snoc_attrs` (fill_a : stroke_a : opts)- where- (fill_a,stroke_a,opts) = drawProperties c dp- ps = svgPath dp p +primPath :: PSUnit u => PathProps -> PrimPath u -> SvgMonad Doc+primPath props pp = (\(a,f) d -> elem_path a (f d)) + <$> pathProps props <*> path pp --- Labels need the coordinate system remapping otherwise--- the will be printed upside down. Both the start point and --- the label itself need transforming.--- --- Also rendering coloured text is convoluted (needing the--- tspan element).--- ----label :: (Real u, Floating u, PSUnit u) - => LabelProps -> Label u -> SvgM Element-label (c,FontAttr sz face) (Label pt entxt ctm) = do - str <- encodedText entxt- let tspan_elt = element_tspan str `snoc_attrs` [ attr_fill c ]- return $ element_text tspan_elt `snoc_attrs` coord_attrs- `snoc_attrs` font_attrs - `snoc_attrs` (fontStyle style)- where- style = svg_font_style face- fam = svg_font_family face- coord_attrs = if ctm == identityCTM then simpleLabelAttrs pt- else transfLabelAttrs pt ctm- font_attrs = [ attr_font_family fam- , attr_font_size sz - ] -simpleLabelAttrs :: PSUnit u => Point2 u -> [Attr] -simpleLabelAttrs pt = [ attr_x x, attr_y y, attr_transform mtrx]- where- P2 x y = coordChange pt- mtrx = val_matrix 1 0 0 (-1) 0 (0::Double)- -transfLabelAttrs :: (Real u, Floating u, PSUnit u) - => Point2 u -> PrimCTM u -> [Attr] -transfLabelAttrs (P2 x y) ctm = - [ attr_x (0::Double), attr_y (0 :: Double), attr_transform vmtrx]+path :: PSUnit u => PrimPath u -> SvgMonad Doc+path (PrimPath start xs) = + pure $ path_m start <+> hsep (map seg xs) where- mtrx = translMatrixRepCTM x y ctm * svg_reflection_matrix- vmtrx = valMatrix mtrx+ seg (PLineTo pt) = path_l pt+ seg (PCurveTo p1 p2 p3) = path_c p1 p2 p3 +-- Return - drawing props, plus a function to close the path (or not). +--+pathProps :: PathProps -> SvgMonad (Doc, Doc -> Doc)+pathProps props = fn props+ where+ fn (CFill rgb) = pure (fillNotStroke rgb, close) -encodedText :: EncodedText -> SvgM String -encodedText entxt = - let xs = getEncodedText entxt in mapM textChunk xs >>= return . concat+ fn (CStroke attrs rgb) = + (\a -> (strokeNotFill rgb <+> a, close)) <$> deltaStrokeAttrs attrs --- | Unfortunately we can\'t readily put a comment in the --- generated SVG when glyph-name lookup fails. Doing similar in --- PostScript is easy because we are emiting /linear/ PostScript --- as we go along. For SVG we are building an abstract syntax --- tree.--- -textChunk :: TextChunk -> SvgM String-textChunk (SText s) = return s-textChunk (EscInt i) = return $ escapeCharCode i-textChunk (EscStr s) = - asks (lookupByGlyphName s) >>= maybe failk (return . escapeCharCode) - where- failk = asks svg_fallback >>= return . escapeCharCode + fn (OStroke attrs rgb) = + (\a -> (strokeNotFill rgb <+> a, id)) <$> deltaStrokeAttrs attrs -escapeCharCode :: CharCode -> String-escapeCharCode i = "&#" ++ show i ++ ";"+ fn (CFillStroke fc attrs sc) =+ (\a -> (fillAndStroke fc sc <+> a, close)) <$> deltaStrokeAttrs attrs + fillNotStroke rgb = attr_fill rgb <+> attr_stroke_none + strokeNotFill rgb = attr_stroke rgb <+> attr_fill_none+ fillAndStroke a b = attr_fill a <+> attr_stroke b+ close = (<+> char 'Z') -fontStyle :: SVGFontStyle -> [Attr]-fontStyle SVG_REGULAR = []-fontStyle SVG_BOLD = [attr_font_weight "bold"]-fontStyle SVG_ITALIC = [attr_font_style "italic"]-fontStyle SVG_BOLD_ITALIC = - [attr_font_weight "bold", attr_font_style "italic"]-fontStyle SVG_OBLIQUE = [attr_font_style "oblique"]-fontStyle SVG_BOLD_OBLIQUE = - [attr_font_weight "bold", attr_font_style "oblique"] --- If w==h the draw the ellipse as a circle -ellipse :: (Real u, Floating u, PSUnit u)- => EllipseProps -> PrimEllipse u -> SvgM Element-ellipse (c,dp) (PrimEllipse pt hw hh ctm) - | hw == hh = return $ element_circle - `snoc_attrs` (circle_attrs ++ style_attrs)- | otherwise = return $ element_ellipse - `snoc_attrs` (ellipse_attrs ++ style_attrs)+++-- Note - if hw==hh then draw the ellipse as a circle.+--+primEllipse :: (Real u, Floating u, PSUnit u)+ => EllipseProps -> PrimEllipse u -> SvgMonad Doc+primEllipse props (PrimEllipse pt hw hh ctm) + | hw == hh = (\a b -> elem_circle (a <+> circle_radius <+> b))+ <$> bracketPrimCTM pt ctm mkCXCY <*> ellipseProps props+ | otherwise = (\a b -> elem_ellipse (a <+> ellipse_radius <+> b))+ <$> bracketPrimCTM pt ctm mkCXCY <*> ellipseProps props where- circle_attrs = if ctm == identityCTM - then simpleCircleAttrs pt hw- else transfCircleAttrs pt hw ctm+ mkCXCY (P2 x y) = pure $ attr_cx x <+> attr_cy y+ + circle_radius = attr_r hw+ ellipse_radius = attr_rx hw <+> attr_ry hh - ellipse_attrs = if ctm == identityCTM - then simpleEllipseAttrs pt hw hh - else transfEllipseAttrs pt hw hh ctm+ - style_attrs = fill_a : stroke_a : opts- where (fill_a,stroke_a,opts) = drawEllipse c dp+ellipseProps :: EllipseProps -> SvgMonad Doc+ellipseProps (EFill rgb) = + pure (attr_fill rgb <+> attr_stroke_none) -simpleCircleAttrs :: PSUnit u => Point2 u -> u -> [Attr]-simpleCircleAttrs (P2 x y) radius = [attr_cx x, attr_cy y, attr_r radius]+ellipseProps (EStroke attrs rgb) = + (\a -> attr_stroke rgb <+> attr_fill_none <+> a) <$> deltaStrokeAttrs attrs -simpleEllipseAttrs :: PSUnit u => Point2 u -> u -> u -> [Attr]-simpleEllipseAttrs (P2 x y) hw hh = - [attr_cx x, attr_cy y, attr_rx hw, attr_ry hh]+ellipseProps (EFillStroke frgb attrs srgb) = + (\a -> attr_fill frgb <+> attr_stroke srgb <+> a) <$> deltaStrokeAttrs attrs -transfCircleAttrs :: (Real u, Floating u, PSUnit u)- => Point2 u -> u -> PrimCTM u -> [Attr]-transfCircleAttrs (P2 x y) radius ctm = - [ attr_cx (0::Double), attr_cy (0::Double), attr_r radius- , attr_transform vmtrx ]++-- Note - Rendering coloured text seemed convoluted +-- (mandating the tspan element). +--+-- TO CHECK - is this really the case?+-- +--++primLabel :: (Real u, Floating u, PSUnit u) + => LabelProps -> PrimLabel u -> SvgMonad Doc+primLabel (LabelProps rgb attrs) (PrimLabel pt etext ctm) = + (\fa ca txt -> elem_text (fa <+> ca) txt)+ <$> deltaFontAttrs attrs <*> bracketPrimCTM pt ctm mkXY + <*> tspan rgb etext where- mtrx = translMatrixRepCTM x y ctm * svg_reflection_matrix- vmtrx = valMatrix mtrx+ mkXY (P2 x y) = pure $ attr_x x <+> attr_y y +tspan :: RGBi -> EncodedText -> SvgMonad Doc+tspan rgb enctext = + (\txt -> elem_tspan (attr_fill rgb) txt) + <$> encodedText enctext -transfEllipseAttrs :: (Real u, Floating u, PSUnit u)- => Point2 u -> u -> u -> PrimCTM u -> [Attr]-transfEllipseAttrs (P2 x y) hw hh ctm = - [ attr_cx (0::Double), attr_cy (0::Double), attr_rx hw, attr_ry hh- , attr_transform vmtrx ]+encodedText :: EncodedText -> SvgMonad Doc+encodedText enctext = hcat <$> mapM textChunk (getEncodedText enctext)+++textChunk :: TextChunk -> SvgMonad Doc+textChunk (SText s) = pure $ text s+textChunk (EscInt i) = pure $ text $ escapeSpecial i+textChunk (EscStr s) = either text text <$> askGlyphName s +++--------------------------------------------------------------------------------+-- Stroke and font attribute delta++deltaStrokeAttrs :: [StrokeAttr] -> SvgMonad Doc+deltaStrokeAttrs xs = hsep <$> mapM df xs where- mtrx = translMatrixRepCTM x y ctm * svg_reflection_matrix- vmtrx = valMatrix mtrx+ df (LineWidth d) = (\inh -> if d==inh then empty + else attr_stroke_width d) + <$> askLineWidth + df (MiterLimit d) = (\inh -> if d==inh then empty + else attr_stroke_miterlimit d)+ <$> askMiterLimit --- A rule of thumb seems to be that SVG (at least SVG in Firefox)--- will try to fill unless told not to. So always label paths--- with @fill=...@ even if fill is @\"none\"@.------ CFill ==> stroke="none" fill="..."--- CStroke ==> stroke="..." fill="none"--- OStroke ==> stroke="..." fill="none"---+ df (LineCap d) = (\inh -> if d==inh then empty + else attr_stroke_linecap d)+ <$> askLineCap -drawProperties :: PSColour c => c -> DrawPath -> (Attr, Attr, [Attr])-drawProperties = fn where- fn c CFill = (attr_fill c, attr_stroke_none, [])- fn c (OStroke xs) = (attr_fill_none, attr_stroke c, strokeAttributes xs)- fn c (CStroke xs) = (attr_fill_none, attr_stroke c, strokeAttributes xs)+ df (LineJoin d) = (\inh -> if d==inh then empty + else attr_stroke_linejoin d)+ <$> askLineJoin -drawEllipse :: PSColour c => c -> DrawEllipse -> (Attr, Attr, [Attr])-drawEllipse = fn where- fn c EFill = (attr_fill c, attr_stroke_none, [])- fn c (EStroke xs) = (attr_fill_none, attr_stroke c, strokeAttributes xs)- + df (DashPattern d) = (\inh -> if d==inh then empty + else makeDashPattern d) + <$> askDashPattern -strokeAttributes :: [StrokeAttr] -> [Attr]-strokeAttributes = foldr fn [] where- fn (LineWidth a) = (:) (attr_stroke_width a)- fn (MiterLimit a) = (:) (attr_stroke_miterlimit a)- fn (LineCap lc) = (:) (attr_stroke_linecap lc)- fn (LineJoin lj) = (:) (attr_stroke_linejoin lj)- fn (DashPattern dp) = dash dp where- dash Solid = (:) (attr_stroke_dasharray_none)- dash (Dash _ []) = (:) (attr_stroke_dasharray_none)- dash (Dash i xs) = (:) (attr_stroke_dashoffset i) . - (:) (attr_stroke_dasharray $ conv xs)- conv = foldr (\(x,y) a -> x:y:a) [] +makeDashPattern :: DashPattern -> Doc+makeDashPattern Solid = attr_stroke_dasharray_none+makeDashPattern (Dash n xs) = + attr_stroke_dashoffset n <+> attr_stroke_dasharray xs -svgPath :: PSUnit u => DrawPath -> Path u -> SvgPath-svgPath (OStroke _) p = pathInstructions p-svgPath _ p = closePath $ pathInstructions p +deltaFontAttrs :: FontAttr -> SvgMonad Doc+deltaFontAttrs fa = + (\inh -> if fa ==inh then empty else makeFontAttrs fa) <$> askFontAttr -pathInstructions :: PSUnit u => Path u -> [String]-pathInstructions (Path (P2 x y) xs) = path_m x y : map pathSegment xs+makeFontAttrs :: FontAttr -> Doc+makeFontAttrs (FontAttr sz face) = + attr_font_family (svg_font_family face) <+> attr_font_size sz + <> suffix (svg_font_style face) + where + suffix SVG_REGULAR = empty -pathSegment :: PSUnit u => PathSegment u -> String-pathSegment (PLineTo (P2 x1 y1)) = path_l x1 y1-pathSegment (PCurveTo (P2 x1 y1) (P2 x2 y2) (P2 x3 y3)) = - path_c x1 y1 x2 y2 x3 y3+ suffix SVG_BOLD = space <> attr_font_weight "bold" + suffix SVG_ITALIC = space <> attr_font_style "italic" + suffix SVG_BOLD_ITALIC = + space <> attr_font_weight "bold" <+> attr_font_style "italic" -frameChange :: PSUnit u => Frame2 u -> Maybe Attr-frameChange fr - | standardFrame fr = Nothing- | otherwise = Just $ attr_transform $ val_matrix a b c d e f - where- CTM a b c d e f = toCTM fr+ suffix SVG_OBLIQUE = space <> attr_font_style "oblique" + suffix SVG_BOLD_OBLIQUE = + space <> attr_font_weight "bold" <+> attr_font_style "oblique" -closePath :: SvgPath -> SvgPath -closePath xs = xs ++ ["Z"] -snoc_attrs :: Element -> [Attr] -> Element-snoc_attrs = flip add_attrs+--------------------------------------------------------------------------------+-- Bracket matrix and PrimCTM trafos +bracketTrafos :: (Real u, Floating u, PSUnit u) + => [AffineTrafo u] -> SvgMonad Doc -> SvgMonad Doc+bracketTrafos xs ma = bracketMatrix (concatTrafos xs) ma +bracketMatrix :: (Fractional u, PSUnit u) + => Matrix3'3 u -> SvgMonad Doc -> SvgMonad Doc+bracketMatrix mtrx ma + | mtrx == identityMatrix = (\doc -> elem_g_no_attrs doc) <$> ma+ | otherwise = (\doc -> elem_g trafo doc) <$> ma+ where+ trafo = attr_transform $ val_matrix mtrx -valMatrix :: PSUnit u => Matrix3'3 u -> String-valMatrix m33 = val_matrix a b c d x y++bracketPrimCTM :: forall u. (Real u, Floating u, PSUnit u)+ => Point2 u -> PrimCTM u + -> (Point2 u -> SvgMonad Doc) -> SvgMonad Doc+bracketPrimCTM pt@(P2 x y) ctm pf + | ctm == identityCTM = pf pt+ | otherwise = (\xy -> xy <+> attr_transform mtrx) <$> pf zeroPt' where- CTM a b c d x y = toCTM m33+ zeroPt' :: Point2 u+ zeroPt' = zeroPt++ mtrx = val_matrix $ translMatrixRepCTM x y ctm
+ src/Wumpus/Core/PageTranslation.hs view
@@ -0,0 +1,65 @@+{-# OPTIONS -Wall #-}++--------------------------------------------------------------------------------+-- |+-- Module : Wumpus.Core.PageTranslation+-- Copyright : (c) Stephen Tetley 2010+-- License : BSD3+--+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC+--+-- Core page translation for SVG.+--+--------------------------------------------------------------------------------+++module Wumpus.Core.PageTranslation+ ( ++ trivialTranslation++ ) where++import Wumpus.Core.AffineTrans+import Wumpus.Core.PictureInternal++++--------------------------------------------------------------------------------+-- trivial translation++-- Rescale the entire image (1,-1), rescale the ellipse and +-- label primitives as they are encoutered (1,-1). No need to +-- worry about scaling the BoundingBox+--++trivialTranslation :: (Num u, Ord u) => Picture u -> Picture u+trivialTranslation pic = scale 1 (-1) (trivPic pic)++trivPic :: Num u => Picture u -> Picture u+trivPic (Leaf lc ones) = Leaf lc $ fmap trivPrim ones+trivPic (Picture lc ones) = Picture lc $ fmap trivPic ones+trivPic (Clip lc pp pic) = Clip lc pp $ trivPic pic+trivPic (Group lc upd pic) = Group lc upd $ trivPic pic++trivPrim :: Num u => Primitive u -> Primitive u+trivPrim (PPath a xl pp) = PPath a xl pp+trivPrim (PLabel a xl lbl) = PLabel a xl (trivLabel lbl)+trivPrim (PEllipse a xl ell) = PEllipse a xl (trivEllipse ell)+++trivLabel :: Num u => PrimLabel u -> PrimLabel u+trivLabel (PrimLabel pt txt ctm) = PrimLabel pt txt (trivPrimCTM ctm)++trivEllipse :: Num u => PrimEllipse u -> PrimEllipse u+trivEllipse (PrimEllipse ctr hw hh ctm) = + PrimEllipse ctr hw hh (trivPrimCTM ctm)++-- Is the translation here just negating the angle with scaling+-- left untouched?+--+trivPrimCTM :: Num u => PrimCTM u -> PrimCTM u+trivPrimCTM (PrimCTM sx sy theta) = PrimCTM sx (-sy) theta+
src/Wumpus/Core/Picture.hs view
@@ -8,180 +8,133 @@ -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 ----- Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC -- -- Construction of pictures, paths and text labels.--- +-- -------------------------------------------------------------------------------- -module Wumpus.Core.Picture - (- - -- * Construction- blankPicture- , frame- , frameWithin- , frameMulti- , multi +module Wumpus.Core.Picture+ ( + -- * Construction+ frame+ , multi , path , lineTo , curveTo- , vertexPath + , vertexPath , curvedPath-- , wumpus_default_font+ , xlinkhref -- * Constructing primitives , Stroke(..) , zostroke , zcstroke- , Fill(..) , zfill- + , Ellipse(..)+ , zellipse+ , clip , TextLabel(..) , ztextlabel - , Ellipse(..)- , zellipse- -- * Operations- , extendBoundary-+ , extendBoundary - -- * Minimal - picture composition- , picMoveBy+ -- * Picture composition , picOver+ , picMoveBy , picBeside++ -- * Illustrating pictures and primitives+ , printPicture , illustrateBounds- , illustrateBoundsPrim + , illustrateBoundsPrim , illustrateControlPoints ) where +import Wumpus.Core.AffineTrans import Wumpus.Core.BoundingBox import Wumpus.Core.Colour+import Wumpus.Core.FormatCombinators import Wumpus.Core.Geometry import Wumpus.Core.GraphicsState import Wumpus.Core.OneList import Wumpus.Core.PictureInternal import Wumpus.Core.PtSize-import Wumpus.Core.TextEncodingInternal+import Wumpus.Core.TextInternal+import Wumpus.Core.Utils -import Data.Semigroup+import Data.AffineSpace -- package: vector-space+import Data.Semigroup -- package: algebra --------------------------------------------------------------------------------- --- Default attributes -psBlack :: PSRgb-psBlack = RGB3 0 0 0- --- aka the standard frame-stdFrame :: Num u => Frame2 u -stdFrame = ortho zeroPt-- -------------------------------------------------------------------------------- -- Construction ---- | Create a blank 'Picture' sized to the supplied bounding box.--- This is useful for spacing rows or columns of pictures.----blankPicture :: Num u => BoundingBox u -> Picture u-blankPicture bb = PicBlank (stdFrame, bb)----- | Lift a 'Primitive' to a 'Picture', located in the standard frame.----frame :: (Real u, Floating u, FromPtSize u) => Primitive u -> Picture u-frame p = Single (stdFrame, boundary p) p ---- | Frame a picture within the supplied bounding box--- --- A text label uses the supplied bounding box as is - no --- clipping is performed if the bounding box is --- smaller than the boundary size of the text. This may --- cause strange overlap for subsequent composite pictures, and--- incorrect bounding box annotations in the prologue of the --- generated EPS file. --- --- Paths and ellipses are bound within the union of the supplied --- bounding box and the inherent bounding box or the path or --- ellipse. Thus the bounding box will never reframed to a --- smaller size than the /natural/ bounding box.----frameWithin :: (Real u, Floating u, FromPtSize u) - => Primitive u -> BoundingBox u -> Picture u-frameWithin p@(PLabel _ _) bb = Single (stdFrame,bb) p-frameWithin p bb = Single (stdFrame,bb `append` boundary p) p------- | Lift a list of primitives to a composite picture, all --- primitives will be located within the standard frame.+-- | Lift a list of primitives to a composite picture. -- -- The order of the list maps to the zorder - the front of the -- list is drawn at the top. -- -- This function throws an error when supplied the empty list. ---frameMulti :: (Real u, Floating u, FromPtSize u) - => [Primitive u] -> Picture u-frameMulti [] = error "Wumpus.Core.Picture.frameMulti - empty list"-frameMulti xs = multi $ map frame xs+frame :: (Real u, Floating u, FromPtSize u) => [Primitive u] -> Picture u+frame [] = error "Wumpus.Core.Picture.frame - empty list"+frame (p:ps) = let (bb,ones) = step p ps in Leaf (bb,[]) ones + where+ step a [] = (boundary a, one a)+ step a (x:xs) = let (bb', rest) = step x xs+ in ( boundary a `append` bb', cons a rest ) --- | Place multiple pictures within the same affine frame.++-- | Place multiple pictures within the standard affine frame. -- -- This function throws an error when supplied the empty list. -- multi :: (Fractional u, Ord u) => [Picture u] -> Picture u-multi ps = Picture (stdFrame, sconcat $ map boundary ps) $ step ps- where - sconcat [] = error err_msg- sconcat (x:xs) = foldr append x xs-- step [x] = one x- step (x:xs) = x `cons` step xs- step _ = error err_msg-- err_msg = "Wumpus.Core.Picture.multi - empty list"+multi [] = error "Wumpus.Core.Picture.multi - empty list"+multi (p:ps) = let (bb,ones) = step p ps in Picture (bb,[]) ones + where+ step a [] = (boundary a, one a)+ step a (x:xs) = let (bb', rest) = step x xs+ in ( boundary a `append` bb', cons a rest ) --- | Create a Path from a start point and a list of --- PathSegments.-path :: Point2 u -> [PathSegment u] -> Path u-path = Path +-- | Create a Path from a start point and a list of PathSegments.+--+path :: Point2 u -> [PrimPathSegment u] -> PrimPath u+path = PrimPath -- | Create a straight-line PathSegment. ---lineTo :: Point2 u -> PathSegment u+lineTo :: Point2 u -> PrimPathSegment u lineTo = PLineTo -- | Create a curved PathSegment. ---curveTo :: Point2 u -> Point2 u -> Point2 u -> PathSegment u+curveTo :: Point2 u -> Point2 u -> Point2 u -> PrimPathSegment u curveTo = PCurveTo -- | Convert the list of vertices to a path of straight line -- segments. ---vertexPath :: [Point2 u] -> Path u+vertexPath :: [Point2 u] -> PrimPath u vertexPath [] = error "Picture.vertexPath - empty point list"-vertexPath (x:xs) = Path x (map PLineTo xs)+vertexPath (x:xs) = PrimPath x (map PLineTo xs) @@ -190,39 +143,29 @@ -- segment thereafter takes 3 points. /Spare/ points at the end -- are discarded. ---curvedPath :: [Point2 u] -> Path u+curvedPath :: [Point2 u] -> PrimPath u curvedPath [] = error "Picture.curvedPath - empty point list"-curvedPath (x:xs) = Path x (fn xs) where- fn (a:b:c:ys) = PCurveTo a b c : fn ys - fn _ = []--- ----- | Constant for the default font, which is @Courier@ (aliased --- to @Courier New@ for SVG) at 24 point.-------wumpus_default_font :: FontAttr-wumpus_default_font = FontAttr 24 face +curvedPath (x:xs) = PrimPath x (step xs) where- face = FontFace { font_name = "Courier"- , svg_font_family = "Courier New"- , svg_font_style = SVG_REGULAR- }+ step (a:b:c:ys) = PCurveTo a b c : step ys + step _ = [] +xlinkhref :: String -> XLink+xlinkhref = XLinkHRef -------------------------------------------------------------------------------- -- Take Paths to Primitives +-- *** Stroke -ostrokePath :: Num u => PSRgb -> [StrokeAttr] -> Path u -> Primitive u-ostrokePath c attrs p = PPath (c, OStroke attrs) p+ostrokePath :: Num u + => RGBi -> [StrokeAttr] -> XLink -> PrimPath u -> Primitive u+ostrokePath rgb attrs xlink p = PPath (OStroke attrs rgb) xlink p -cstrokePath :: Num u => PSRgb -> [StrokeAttr] -> Path u -> Primitive u-cstrokePath c attrs p = PPath (c, CStroke attrs) p+cstrokePath :: Num u + => RGBi -> [StrokeAttr] -> XLink -> PrimPath u -> Primitive u+cstrokePath rgb attrs xlink p = PPath (CStroke attrs rgb) xlink p -- | Create a open, stroked path (@ostroke@) or a closed, stroked -- path (@cstroke@).@@ -231,79 +174,74 @@ -- the path more convenient. -- class Stroke t where- ostroke :: Num u => t -> Path u -> Primitive u- cstroke :: Num u => t -> Path u -> Primitive u+ ostroke :: Num u => t -> PrimPath u -> Primitive u+ cstroke :: Num u => t -> PrimPath u -> Primitive u instance Stroke () where- ostroke () = ostrokePath psBlack []- cstroke () = cstrokePath psBlack []--instance Stroke (RGB3 Double) where- ostroke c = ostrokePath (psColour c) []- cstroke c = cstrokePath (psColour c) []--instance Stroke (HSB3 Double) where- ostroke c = ostrokePath (psColour c) []- cstroke c = cstrokePath (psColour c) []--instance Stroke (Gray Double) where- ostroke c = ostrokePath (psColour c) []- cstroke c = cstrokePath (psColour c) []+ ostroke () = ostrokePath black [] NoLink+ cstroke () = cstrokePath black [] NoLink +instance Stroke RGBi where+ ostroke rgb = ostrokePath rgb [] NoLink+ cstroke rgb = cstrokePath rgb [] NoLink instance Stroke StrokeAttr where- ostroke x = ostrokePath psBlack [x]- cstroke x = cstrokePath psBlack [x]+ ostroke x = ostrokePath black [x] NoLink+ cstroke x = cstrokePath black [x] NoLink instance Stroke [StrokeAttr] where- ostroke xs = ostrokePath psBlack xs- cstroke xs = cstrokePath psBlack xs+ ostroke xs = ostrokePath black xs NoLink+ cstroke xs = cstrokePath black xs NoLink +instance Stroke XLink where+ ostroke xlink = ostrokePath black [] xlink+ cstroke xlink = cstrokePath black [] xlink -instance Stroke (RGB3 Double,StrokeAttr) where- ostroke (c,x) = ostrokePath (psColour c) [x]- cstroke (c,x) = cstrokePath (psColour c) [x]+instance Stroke (RGBi,StrokeAttr) where+ ostroke (rgb,x) = ostrokePath rgb [x] NoLink+ cstroke (rgb,x) = cstrokePath rgb [x] NoLink -instance Stroke (HSB3 Double,StrokeAttr) where- ostroke (c,x) = ostrokePath (psColour c) [x]- cstroke (c,x) = cstrokePath (psColour c) [x]+instance Stroke (RGBi,[StrokeAttr]) where+ ostroke (rgb,xs) = ostrokePath rgb xs NoLink+ cstroke (rgb,xs) = cstrokePath rgb xs NoLink -instance Stroke (Gray Double,StrokeAttr) where- ostroke (c,x) = ostrokePath (psColour c) [x]- cstroke (c,x) = cstrokePath (psColour c) [x]+instance Stroke (RGBi,XLink) where+ ostroke (rgb,xlink) = ostrokePath rgb [] xlink+ cstroke (rgb,xlink) = cstrokePath rgb [] xlink -instance Stroke (RGB3 Double,[StrokeAttr]) where- ostroke (c,xs) = ostrokePath (psColour c) xs- cstroke (c,xs) = cstrokePath (psColour c) xs+instance Stroke (StrokeAttr,XLink) where+ ostroke (x,xlink) = ostrokePath black [x] xlink+ cstroke (x,xlink) = cstrokePath black [x] xlink -instance Stroke (HSB3 Double,[StrokeAttr]) where- ostroke (c,xs) = ostrokePath (psColour c) xs- cstroke (c,xs) = cstrokePath (psColour c) xs+instance Stroke ([StrokeAttr],XLink) where+ ostroke (xs,xlink) = ostrokePath black xs xlink+ cstroke (xs,xlink) = cstrokePath black xs xlink -instance Stroke (Gray Double,[StrokeAttr]) where- ostroke (c,xs) = ostrokePath (psColour c) xs- cstroke (c,xs) = cstrokePath (psColour c) xs+instance Stroke (RGBi,StrokeAttr,XLink) where+ ostroke (rgb,x,xlink) = ostrokePath rgb [x] xlink+ cstroke (rgb,x,xlink) = cstrokePath rgb [x] xlink +instance Stroke (RGBi,[StrokeAttr],XLink) where+ ostroke (rgb,xs,xlink) = ostrokePath rgb xs xlink+ cstroke (rgb,xs,xlink) = cstrokePath rgb xs xlink + -- | Create an open stoke coloured black. ---zostroke :: Num u => Path u -> Primitive u-zostroke = ostrokePath psBlack []+zostroke :: Num u => PrimPath u -> Primitive u+zostroke = ostrokePath black [] NoLink -- | Create a closed stroke coloured black. ---zcstroke :: Num u => Path u -> Primitive u-zcstroke = cstrokePath psBlack []----+zcstroke :: Num u => PrimPath u -> Primitive u+zcstroke = cstrokePath black [] NoLink +-- *** Fill -fillPath :: Num u => PSRgb -> Path u -> Primitive u-fillPath c p = PPath (c,CFill) p+fillPath :: Num u => RGBi -> XLink -> PrimPath u -> Primitive u+fillPath rgb xlink p = PPath (CFill rgb) xlink p -- | Create a filled path (@fill@). Fills only have one -- property - colour. But there are various representations of @@ -312,36 +250,51 @@ -- @ fill () @ will fill with the default colour - black. -- class Fill t where- fill :: Num u => t -> Path u -> Primitive u+ fill :: Num u => t -> PrimPath u -> Primitive u -instance Fill () where fill () = fillPath psBlack -instance Fill (RGB3 Double) where fill = fillPath . psColour-instance Fill (HSB3 Double) where fill = fillPath . psColour-instance Fill (Gray Double) where fill = fillPath . psColour+instance Fill () where fill () = fillPath black NoLink+instance Fill RGBi where fill rgb = fillPath rgb NoLink+instance Fill XLink where fill xlink = fillPath black xlink +instance Fill (RGBi,XLink) where+ fill (rgb,xlink) = fillPath rgb xlink++ -- | Create a filled path coloured black. -zfill :: Num u => Path u -> Primitive u-zfill = fillPath psBlack+zfill :: Num u => PrimPath u -> Primitive u+zfill = fillPath black NoLink -------------------------------------------------------------------------------- -- Clipping -- | Clip a picture with respect to the supplied path. ---clip :: (Num u, Ord u) => Path u -> Picture u -> Picture u-clip cp p = Clip (ortho zeroPt, boundary cp) cp p-+clip :: (Num u, Ord u) => PrimPath u -> Picture u -> Picture u+clip cp p = Clip (pathBoundary cp, []) cp p -------------------------------------------------------------------------------- -- Labels to primitive -mkTextLabel :: Num u => PSRgb -> FontAttr -> String -> Point2 u -> Primitive u-mkTextLabel c attr txt pt = PLabel (c,attr) lbl +mkTextLabel :: Num u + => RGBi -> FontAttr -> XLink -> String -> Point2 u -> Primitive u+mkTextLabel rgb attr xlink txt pt = PLabel (LabelProps rgb attr) xlink lbl where- lbl = Label pt (lexLabel txt) identityCTM+ lbl = PrimLabel pt (lexLabel txt) identityCTM +-- | Constant for the default font, which is @Courier@ (aliased +-- to @Courier New@ for SVG) at 14 point.+--+--+wumpus_default_font :: FontAttr+wumpus_default_font = FontAttr 14 face + where+ face = FontFace { font_name = "Courier"+ , svg_font_family = "Courier New"+ , svg_font_style = SVG_REGULAR+ } + -- | Create a text label. The string should not contain newline -- or tab characters. Use 'multilabel' to create text with -- multiple lines.@@ -349,7 +302,7 @@ -- @textlabel@ is overloaded to make attributing the label more -- convenient. ----- Unless a 'FontAttr' is specified, the label will use 12pt +-- Unless a 'FontAttr' is specified, the label will use 14pt -- Courier. -- -- The supplied point is is the bottom left corner.@@ -359,45 +312,47 @@ instance TextLabel () where - textlabel () = mkTextLabel psBlack wumpus_default_font+ textlabel () = mkTextLabel black wumpus_default_font NoLink -instance TextLabel (RGB3 Double) where- textlabel c = mkTextLabel (psColour c) wumpus_default_font+instance TextLabel RGBi where+ textlabel rgb = mkTextLabel rgb wumpus_default_font NoLink -instance TextLabel (HSB3 Double) where- textlabel c = mkTextLabel (psColour c) wumpus_default_font+instance TextLabel FontAttr where+ textlabel a = mkTextLabel black a NoLink -instance TextLabel (Gray Double) where- textlabel c = mkTextLabel (psColour c) wumpus_default_font+instance TextLabel XLink where+ textlabel xlink = mkTextLabel black wumpus_default_font xlink -instance TextLabel FontAttr where- textlabel a = mkTextLabel psBlack a -instance TextLabel (RGB3 Double,FontAttr) where- textlabel (c,a) = mkTextLabel (psColour c) a+instance TextLabel (RGBi,FontAttr) where+ textlabel (rgb,a) = mkTextLabel rgb a NoLink -instance TextLabel (HSB3 Double,FontAttr) where- textlabel (c,a) = mkTextLabel (psColour c) a+instance TextLabel (RGBi,XLink) where+ textlabel (rgb,xlink) = mkTextLabel rgb wumpus_default_font xlink -instance TextLabel (Gray Double,FontAttr) where- textlabel (c,a) = mkTextLabel (psColour c) a+instance TextLabel (FontAttr,XLink) where+ textlabel (a,xlink) = mkTextLabel black a xlink --- | Create a label where the font is @Courier@, text size is 24pt+instance TextLabel (RGBi,FontAttr,XLink) where+ textlabel (rgb,a,xlink) = mkTextLabel rgb a xlink++-- | Create a label where the font is @Courier@, text size is 14pt -- and colour is black.+-- ztextlabel :: Num u => String -> Point2 u -> Primitive u-ztextlabel = mkTextLabel psBlack wumpus_default_font-+ztextlabel = mkTextLabel black wumpus_default_font NoLink -------------------------------------------------------------------------------- mkEllipse :: Num u - => PSRgb -> DrawEllipse -> u -> u -> Point2 u -> Primitive u-mkEllipse c dp hw hh pt = PEllipse (c,dp) (PrimEllipse pt hw hh identityCTM)+ => EllipseProps -> XLink -> u -> u -> Point2 u -> Primitive u+mkEllipse props xlink hw hh pt = + PEllipse props xlink (PrimEllipse pt hw hh identityCTM) ellipseDefault :: EllipseProps-ellipseDefault = (psBlack, EFill)+ellipseDefault = EFill black -- | Create an ellipse, the ellipse will be filled unless the @@ -420,65 +375,45 @@ class Ellipse t where ellipse :: Fractional u => t -> u -> u -> Point2 u -> Primitive u -instance Ellipse () where ellipse () = zellipse-instance Ellipse DrawEllipse where ellipse dp = mkEllipse psBlack dp -instance Ellipse StrokeAttr where - ellipse = mkEllipse psBlack . EStroke . return--instance Ellipse [StrokeAttr] where - ellipse = mkEllipse psBlack . EStroke--instance Ellipse (RGB3 Double) where - ellipse c = mkEllipse (psColour c) EFill--instance Ellipse (HSB3 Double) where - ellipse c = mkEllipse (psColour c) EFill--instance Ellipse (Gray Double) where - ellipse c = mkEllipse (psColour c) EFill---instance Ellipse (RGB3 Double,DrawEllipse) where - ellipse (c,dp) = mkEllipse (psColour c) dp+instance Ellipse () where ellipse () = zellipse -instance Ellipse (HSB3 Double,DrawEllipse) where - ellipse (c,dp) = mkEllipse (psColour c) dp+instance Ellipse RGBi where + ellipse rgb = mkEllipse (EFill rgb) NoLink -instance Ellipse (Gray Double,DrawEllipse) where - ellipse (c,dp) = mkEllipse (psColour c) dp+instance Ellipse StrokeAttr where+ ellipse x = mkEllipse (EStroke [x] black) NoLink +instance Ellipse [StrokeAttr] where+ ellipse xs = mkEllipse (EStroke xs black) NoLink -instance Ellipse (RGB3 Double,StrokeAttr) where - ellipse (c,x) = mkEllipse (psColour c) (EStroke [x])+instance Ellipse XLink where + ellipse xlink = mkEllipse (EFill black) xlink -instance Ellipse (HSB3 Double,StrokeAttr) where - ellipse (c,x) = mkEllipse (psColour c) (EStroke [x])+instance Ellipse (RGBi,StrokeAttr) where+ ellipse (rgb,x) = mkEllipse (EStroke [x] rgb) NoLink -instance Ellipse (Gray Double,StrokeAttr) where - ellipse (c,x) = mkEllipse (psColour c) (EStroke [x])+instance Ellipse (RGBi,[StrokeAttr]) where+ ellipse (rgb,xs) = mkEllipse (EStroke xs rgb) NoLink -instance Ellipse (RGB3 Double,[StrokeAttr]) where - ellipse (c,xs) = mkEllipse (psColour c) (EStroke xs)+instance Ellipse (RGBi,XLink) where+ ellipse (rgb,xlink) = mkEllipse (EFill rgb) xlink -instance Ellipse (HSB3 Double,[StrokeAttr]) where - ellipse (c,xs) = mkEllipse (psColour c) (EStroke xs)+instance Ellipse (StrokeAttr,XLink) where+ ellipse (x,xlink) = mkEllipse (EStroke [x] black) xlink -instance Ellipse (Gray Double,[StrokeAttr]) where - ellipse (c,xs) = mkEllipse (psColour c) (EStroke xs)+instance Ellipse ([StrokeAttr],XLink) where+ ellipse (xs,xlink) = mkEllipse (EStroke xs black) xlink +instance Ellipse (RGBi,[StrokeAttr],XLink) where+ ellipse (rgb,xs,xlink) = mkEllipse (EStroke xs rgb) xlink -- | Create a black, filled ellipse. zellipse :: Num u => u -> u -> Point2 u -> Primitive u-zellipse = uncurry mkEllipse ellipseDefault-+zellipse hw hh pt = mkEllipse ellipseDefault NoLink hw hh pt ------------------------------------------------------------------------------------ Operations on pictures and paths---+-- Operations -- | Extend the bounding box of a picture. --@@ -487,7 +422,7 @@ -- This function cannot be used to shrink a boundary. -- extendBoundary :: (Num u, Ord u) => u -> u -> Picture u -> Picture u-extendBoundary x y = mapLocale (\(fr,bb) -> (fr, extBB (posve x) (posve y) bb)) +extendBoundary x y = mapLocale (\(bb,xs) -> (extBB (posve x) (posve y) bb, xs)) where extBB x' y' (BBox (P2 x0 y0) (P2 x1 y1)) = BBox pt1 pt2 where pt1 = P2 (x0-x') (y0-y')@@ -507,16 +442,16 @@ -- neither picture will be moved. -- picOver :: (Num u, Ord u) => Picture u -> Picture u -> Picture u-a `picOver` b = Picture (ortho zeroPt, bb) (cons a $ one b) +a `picOver` b = Picture (bb,[]) (cons a $ one b) where- bb = union (boundary a) (boundary b)+ bb = boundary a `append` boundary b -- | 'picMoveBy' : @ picture -> vector -> picture @ -- -- Move a picture by the supplied vector. ---picMoveBy :: Num u => Picture u -> Vec2 u -> Picture u-p `picMoveBy` v = v `movePic` p +picMoveBy :: (Num u, Ord u) => Picture u -> Vec2 u -> Picture u+p `picMoveBy` (V2 dx dy) = translate dx dy p -- | 'picBeside' : @ picture -> picture -> picture @ --@@ -526,16 +461,27 @@ picBeside :: (Num u, Ord u) => Picture u -> Picture u -> Picture u a `picBeside` b = a `picOver` (b `picMoveBy` v) where - v = hvec $ rightPlane (boundary a) - leftPlane (boundary b) + (P2 x1 _) = ur_corner $ boundary a+ (P2 x2 _) = ll_corner $ boundary b + v = hvec $ x1 - x2 +--------------------------------------------------------------------------------+-- Illustrating pictures and primitives++-- | Print the syntax tree of a Picture to the console.+--+printPicture :: (Num u, PSUnit u) => Picture u -> IO ()+printPicture pic = putStrLn (show $ format pic) >> putStrLn []++ -- | 'illustrateBounds' : @ colour -> picture -> picture @ -- -- Draw the picture on top of an image of its bounding box. -- The bounding box image will be drawn in the supplied colour. -- illustrateBounds :: (Real u, Floating u, FromPtSize u) - => DRGB -> Picture u -> Picture u-illustrateBounds rgb p = p `picOver` (frameMulti $ boundsPrims rgb p) + => RGBi -> Picture u -> Picture u+illustrateBounds rgb p = p `picOver` (frame $ boundsPrims rgb p) -- | 'illustrateBoundsPrim' : @ colour -> primitive -> picture @@@ -546,26 +492,26 @@ -- The result will be lifted from Primitive to Picture. -- illustrateBoundsPrim :: (Real u, Floating u, FromPtSize u) - => DRGB -> Primitive u -> Picture u-illustrateBoundsPrim rgb p = frameMulti (boundsPrims rgb p ++ [p])+ => RGBi -> Primitive u -> Picture u+illustrateBoundsPrim rgb p = frame (p : boundsPrims rgb p) --- Note - above has to use snoc (++ [p]) to get the picture to--- draw above the bounding box image. -- | Draw a the rectangle of a bounding box, plus cross lines -- joining the corners. -- boundsPrims :: (Num u, Ord u, Boundary t, u ~ DUnit t) - => DRGB -> t -> [Primitive u]+ => RGBi -> t -> [Primitive u] boundsPrims rgb a = [ bbox_rect, bl_to_tr, br_to_tl ] where- (bl,br,tr,tl) = corners $ boundary a- bbox_rect = cstroke rgb $ vertexPath [bl,br,tr,tl]- bl_to_tr = ostroke rgb $ vertexPath [bl,tr]- br_to_tl = ostroke rgb $ vertexPath [br,tl]+ (bl,br,tr,tl) = boundaryCorners $ boundary a+ bbox_rect = cstroke (rgb, line_attr) $ vertexPath [bl,br,tr,tl]+ bl_to_tr = ostroke (rgb, line_attr) $ vertexPath [bl,tr]+ br_to_tl = ostroke (rgb, line_attr) $ vertexPath [br,tl] + line_attr = [LineCap $ CapRound, DashPattern $ Dash 0 [(1,2)]] + -- | Generate the control points illustrating the Bezier -- curves within a picture. -- @@ -577,12 +523,12 @@ -- @arc@ command. -- illustrateControlPoints :: (Real u, Floating u, FromPtSize u)- => DRGB -> Primitive u -> Picture u+ => RGBi -> Primitive u -> Picture u illustrateControlPoints rgb prim = step prim where- step (PEllipse _ e) = frameMulti (prim : ellipseCtrlLines rgb e)- step (PPath _ p) = frameMulti (prim : pathCtrlLines rgb p)- step _ = frame prim+ step (PEllipse _ _ e) = frame (prim : ellipseCtrlLines rgb e)+ step (PPath _ _ p) = frame (prim : pathCtrlLines rgb p)+ step _ = frame [prim] -- Genrate lines illustrating the control points of curves on -- a Path.@@ -592,15 +538,15 @@ -- -- Nothing is generated for a straight line. ---pathCtrlLines :: (Num u, Ord u) => DRGB -> Path u -> [Primitive u]-pathCtrlLines rgb (Path start ss) = step start ss+pathCtrlLines :: (Num u, Ord u) => RGBi -> PrimPath u -> [Primitive u]+pathCtrlLines rgb (PrimPath start ss) = step start ss where -- trail the current end point through the recursion... step _ [] = [] step _ (PLineTo e:xs) = step e xs step s (PCurveTo c1 c2 e:xs) = mkLine s c1 : mkLine c2 e : step e xs - mkLine s e = ostroke rgb (Path s [lineTo e]) + mkLine s e = ostroke rgb (PrimPath s [lineTo e]) -- Generate lines illustrating the control points of an @@ -610,7 +556,7 @@ -- start-point to control-point1; control-point2 to end-point -- ellipseCtrlLines :: (Real u, Floating u) - => DRGB -> PrimEllipse u -> [Primitive u]+ => RGBi -> PrimEllipse u -> [Primitive u] ellipseCtrlLines rgb pe = start all_points where -- list in order: @@ -624,6 +570,41 @@ rest s (c1:c2:e:xs) = mkLine s c1 : mkLine c2 e : rest e xs rest _ _ = [] - mkLine s e = ostroke rgb (Path s [lineTo e]) + mkLine s e = ostroke rgb (PrimPath s [lineTo e]) +++-- | Get the control points as a list+-- +-- There are no duplicates in the list except for the final +-- /wrap-around/. We take 4 points initially (start,cp1,cp2,end)+-- then (cp1,cp2,end) for the other three quadrants.+--+ellipseControlPoints :: (Floating u, Real u)+ => PrimEllipse u -> [Point2 u]+ellipseControlPoints (PrimEllipse (P2 x y) hw hh ctm) = + map (disp . (new_mtrx *#)) circ+ where+ disp = (.+^ V2 x y)+ (radius,(dx,dy)) = circleScalingProps hw hh+ new_mtrx = matrixRepCTM $ scaleCTM dx dy ctm+ circ = bezierCircle 1 radius (P2 0 0)++ -- subdivide the bezierCircle with 1 to get two+ -- control points per quadrant. +++--+-- I don't know how to calculate bezier arcs (and thus control+-- points) for an ellipse but I know how to do it for a circle...+--+-- So a make a circle with the largest of half-width and +-- half-height then apply a scale to the points+-- +circleScalingProps :: (Fractional u, Ord u) => u -> u -> (u,(u,u))+circleScalingProps hw hh = (radius, (dx,dy))+ where+ radius = max hw hh+ (dx,dy) = if radius == hw then (1, rescale (0,hw) (0,1) hh)+ else (rescale (0,hh) (0,1) hw, 1)
src/Wumpus/Core/PictureInternal.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleContexts #-} {-# OPTIONS -Wall #-} --------------------------------------------------------------------------------@@ -8,80 +7,79 @@ -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 ----- Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC ----- Internal representation of Pictures --- +-- Internal representation of Pictures.+-- -------------------------------------------------------------------------------- -module Wumpus.Core.PictureInternal - (- -- * Data types++module Wumpus.Core.PictureInternal+ ( + Picture(..) , DPicture+ , Locale+ , AffineTrafo(..)+ , GSUpdate(..)+ , Primitive(..) , DPrimitive- , Path(..)- , DPath- , PathSegment(..)- , DPathSegment- , Label(..)- , DLabel- , PrimEllipse(..)- , DPrimEllipse- , PrimCTM-- , PathProps -- hide in Wumpus.Core export?- , LabelProps -- hide in Wumpus.Core export?- , EllipseProps -- - , DrawPath(..) -- hide in Wumpus.Core export?- , DrawEllipse(..)- , Locale - - -- * Type class-- , PSUnit(..)+ , XLink(..) + , PrimPath(..)+ , DPrimPath+ , PathProps(..)+ , PrimPathSegment(..)+ , DPrimPathSegment+ , PrimLabel(..)+ , DPrimLabel+ , LabelProps(..)+ , PrimEllipse(..)+ , EllipseProps(..)+ , PrimCTM(..) - -- * Transformations on Primitives- , translatePrimitive- , rotatePrimitive- , scalePrimitive- , uniformScalePrimitive+ , pathBoundary+ , mapLocale -- * PrimCTM , identityCTM , scaleCTM+ , rotateCTM , matrixRepCTM , translMatrixRepCTM- - -- * Extras- , mapLocale- , movePic- , moveLocale- , extractFrame- , repositionProperties- , ellipseControlPoints + , rotatePrimitive+ , scalePrimitive+ , uniformScalePrimitive+ , translatePrimitive++ -- * Additional operations+ , concatTrafos+ , deconsMatrix+ , repositionDeltas++ ) where import Wumpus.Core.AffineTrans import Wumpus.Core.BoundingBox+import Wumpus.Core.Colour import Wumpus.Core.FontSize+import Wumpus.Core.FormatCombinators import Wumpus.Core.Geometry import Wumpus.Core.GraphicsState import Wumpus.Core.OneList import Wumpus.Core.PtSize-import Wumpus.Core.TextEncodingInternal+import Wumpus.Core.TextInternal import Wumpus.Core.Utils -import Data.AffineSpace-import Data.Semigroup+import Data.AffineSpace -- package: vector-space -import Text.PrettyPrint.Leijen -- package: wl-pprint+import qualified Data.Foldable as F @@ -107,31 +105,71 @@ -- optimization to avoid generating excessive graphics state -- changes in the PostScript code. ----- Apropos the constructors, Picture is a simple non-empty +-- Omitting some details, Picture is a simple non-empty -- leaf-labelled rose tree via: -- --- > Single (aka leaf) | Picture (OneList tree)+-- > Leaf primitives | Picture [tree] -- -- Where OneList is a variant of the standard list type that -- disallows empty lists. -- -- The additional constructors are convenience: ----- @PicBlank@ has a bounding box but no content and is useful for--- some picture language operations (e.g. @hsep@).--- -- @Clip@ nests a picture (tree) inside a clipping path. --+-- The @Group@ constructor allows local shared graphics state +-- updates for the SVG renderer - in some instances this can +-- improve the code size of the generated SVG.+--+data Picture u = Leaf (Locale u) (OneList (Primitive u))+ | Picture (Locale u) (OneList (Picture u))+ | Clip (Locale u) (PrimPath u) (Picture u)+ | Group (Locale u) GSUpdate (Picture u)+ deriving (Show) -data Picture u = PicBlank (Locale u)- | Single (Locale u) (Primitive u)- | Picture (Locale u) (OneList (Picture u))- | Clip (Locale u) (Path u) (Picture u)- deriving (Eq,Show) +-- | Update the graphics state for SVG rendering. +-- +-- Note - this does not change how any of the Primitives are +-- drawn, nor does it change the default colour or font style. +-- It is solely a backdoor into the SVG renderer to potential +-- allow some code size reductions.+--+newtype GSUpdate = GSUpdate { getGSU :: GraphicsState -> GraphicsState } +instance Show GSUpdate where+ show _ = "*function*"++-- | Locale = (bounding box * current translation matrix)+-- +-- Pictures (and sub-pictures) are located frame consisting of a +-- bounding box and a translation matrix (represented as a list +-- of affine transformations). So that pictures can be arranged +-- via vertical and horizontal composition their bounding box is +-- cached.+--+-- In Wumpus, affine transformations (scalings, rotations...)+-- transform the CTM rather than the constituent points of +-- the primitives. Changes of CTM are transmitted to PostScript+-- as @concat@ commands (and matrix transforms in SVG).+-- +-- So that picture composition is remains stable under affine+-- transformation, the corners of bounding boxes are transformed+-- pointwise when the picture is scaled, rotated etc.+--+type Locale u = (BoundingBox u, [AffineTrafo u])+ type DPicture = Picture Double +-- | Affine transformations are represented as /syntax/ so they+-- can be manipulated easily.+--+data AffineTrafo u = Matrix (Matrix3'3 u)+ | Rotate Radian+ | RotAbout Radian (Point2 u)+ | Scale u u+ | Translate u u+ deriving (Eq,Show) -- | Wumpus\'s drawings are built from two fundamental -- primitives: paths (line segments and Bezier curves) and @@ -142,8 +180,8 @@ -- least eight curves. This is inconvenient for drawing dots -- which can otherwise be drawn with a single @arc@ command. -- --- Wumpus does not follow PostScript and employ arcs as general --- path primitives - they are used only to draw ellipses. This +-- Wumpus does not follow PostScript employing arc as a general +-- path primitive - arcs are used only to draw ellipses. This -- is because arcs do not enjoy the nice properties of Bezier -- curves, whereby the affine transformation of a Bezier curve -- can simply be achieved by the affine transformation of it\'s @@ -156,41 +194,68 @@ -- Though typically for affine transformations a Fractional -- constraint is also obliged. ----data Primitive u = PPath PathProps (Path u)- | PLabel LabelProps (Label u) - | PEllipse EllipseProps (PrimEllipse u)+data Primitive u = PPath PathProps XLink (PrimPath u)+ | PLabel LabelProps XLink (PrimLabel u)+ | PEllipse EllipseProps XLink (PrimEllipse u) deriving (Eq,Show) type DPrimitive = Primitive Double +-- | Primitives can be annotated with hyperlinks in SVG output.+--+data XLink = NoLink+ | XLinkHRef String+ deriving (Eq,Show) --- | Path - start point and a list of path segments.+-- | PrimPath - start point and a list of path segments. ---data Path u = Path (Point2 u) [PathSegment u]+data PrimPath u = PrimPath (Point2 u) [PrimPathSegment u] deriving (Eq,Show) -type DPath = Path Double+type DPrimPath = PrimPath Double --- | PathSegment - either a cubic Bezier curve or a line.--- -data PathSegment u = PCurveTo (Point2 u) (Point2 u) (Point2 u)- | PLineTo (Point2 u)+-- | PrimPathSegment - either a cubic Bezier curve or a line.+--+data PrimPathSegment u = PCurveTo (Point2 u) (Point2 u) (Point2 u)+ | PLineTo (Point2 u) deriving (Eq,Show) -type DPathSegment = PathSegment Double+type DPrimPathSegment = PrimPathSegment Double --- | Label - represented by bottom left corner and text.+-- | Note when drawn /filled/ and drawn /stroked/ the same+-- polygon will have (slightly) different size: ---data Label u = Label - { label_bottom_left :: Point2 u- , label_text :: EncodedText- , label_ctm :: PrimCTM u+-- * A filled shape fills /within/ the boundary of the shape+--+-- * A stroked shape draws a pen line around the boundary+-- of the shape. The actual size depends on the thickness+-- of the line (stroke width).+--+data PathProps = CFill RGBi + | CStroke [StrokeAttr] RGBi+ | OStroke [StrokeAttr] RGBi+ | CFillStroke RGBi [StrokeAttr] RGBi+ deriving (Eq,Show)+++-- | Label - represented by /baseline/ left point and text.+--+data PrimLabel u = PrimLabel + { label_baseline_left :: Point2 u+ , label_text :: EncodedText+ , label_ctm :: PrimCTM u } deriving (Eq,Show) -type DLabel = Label Double+type DPrimLabel = PrimLabel Double +data LabelProps = LabelProps + { label_colour :: RGBi+ , label_font :: FontAttr+ }+ deriving (Eq,Ord,Show)++ -- Ellipse represented by center and half_width * half_height -- data PrimEllipse u = PrimEllipse @@ -201,9 +266,20 @@ } deriving (Eq,Show) -type DPrimEllipse = PrimEllipse Double +-- | Ellipses and circles are always closed.+--+data EllipseProps = EFill RGBi+ | EStroke [StrokeAttr] RGBi + -- Note - first colour fill, second colour stroke.+ | EFillStroke RGBi [StrokeAttr] RGBi + deriving (Eq,Show) +++-- Note - primitives are not considered to exist in an affine +-- space. +-- data PrimCTM u = PrimCTM { ctm_scale_x :: u , ctm_scale_y :: u@@ -212,146 +288,251 @@ deriving (Eq,Show) --- | Note when drawn /filled/ and drawn /stroked/ the same --- polygon will have (slightly) different size: --- --- * A filled shape fills /within/ the boundary of the shape--- --- * A stroked shape draws a pen line around the boundary --- of the shape. The actual size depends on the thickness--- of the line (stroke width).----data DrawPath = CFill | CStroke [StrokeAttr] | OStroke [StrokeAttr]- deriving (Eq,Show)+--------------------------------------------------------------------------------+-- family instances --- | Ellipses and circles are always closed.-data DrawEllipse = EFill | EStroke [StrokeAttr]- deriving (Eq,Show)+type instance DUnit (Picture u) = u+type instance DUnit (Primitive u) = u+type instance DUnit (PrimEllipse u) = u -type PathProps = (PSRgb, DrawPath)-type LabelProps = (PSRgb, FontAttr)-type EllipseProps = (PSRgb, DrawEllipse)+--------------------------------------------------------------------------------+-- instances --- | Locale = (current frame x bounding box)--- --- Pictures (and sub-pictures) are located within an affine frame.--- So pictures can be arranged (vertical and horizontal --- composition) their bounding box is cached.------ In Wumpus, affine transformations (scalings, rotations...)--- transform the frame rather than the constituent points of --- the primitives. Changes of frame are transmitted to PostScript--- as @concat@ commands (and matrix transforms in SVG) - the --- @point-in-world-coordinate@ of a point on a path is never --- calculated.--- --- So that picture composition is remains stable under affine--- transformation, the corners of bounding boxes are transformed--- pointwise when the picture is scaled, rotated etc.----type Locale u = (Frame2 u, BoundingBox u) +instance (Num u, PSUnit u) => Format (Picture u) where+ format (Leaf m prims) = indent 2 $ vcat [ text "** Leaf-pic **"+ , fmtLocale m + , fmtPrims prims ] + format (Picture m pics) = indent 2 $ vcat [ text "** Tree-pic **"+ , fmtLocale m+ , fmtPics pics ]+ + format (Clip m path pic) = indent 2 $ vcat [ text "** Clip-path **"+ , fmtLocale m+ , format path+ , format pic ] + format (Group m _ pic) = indent 2 $ vcat [ text "** Group **"+ , fmtLocale m+ , format pic ] ------------------------------------------------------------------------------------ Pretty printing+fmtPics :: PSUnit u => OneList (Picture u) -> Doc+fmtPics ones = snd $ F.foldl' fn (0,empty) ones+ where+ fn (n,acc) e = (n+1, vcat [ acc, text "-- " <+> int n, format e, line]) -instance (Num u, Pretty u) => Pretty (Picture u) where- pretty (PicBlank m) = text "*BLANK*" <+> ppLocale m- pretty (Single m prim) = ppLocale m <$> indent 2 (pretty prim)- pretty (Picture m ones) = - ppLocale m <$> indent 2 (list $ toListF pretty ones)+fmtPrims :: PSUnit u => OneList (Primitive u) -> Doc+fmtPrims ones = snd $ F.foldl' fn (0,empty) ones+ where+ fn (n,acc) e = (n+1, vcat [ acc, text "-- leaf" <+> int n, format e, line]) - pretty (Clip m cpath p) = - text "Clip:" <+> ppLocale m <$> indent 2 (pretty cpath)- <$> indent 2 (pretty p)+fmtLocale :: (Num u, PSUnit u) => Locale u -> Doc+fmtLocale (bb,_) = format bb -ppLocale :: (Num u, Pretty u) => Locale u -> Doc-ppLocale (fr,bb) = align (ppfr <$> pretty bb) where- ppfr = if standardFrame fr then text "*std-frame*" else pretty fr +instance PSUnit u => Format (Primitive u) where+ format (PPath props _ p) = + indent 2 $ vcat [ text "path:" <+> format props, format p ] -instance Pretty u => Pretty (Primitive u) where- pretty (PPath _ p) = pretty "path:" <+> pretty p- pretty (PLabel _ lbl) = pretty lbl- pretty (PEllipse _ e) = pretty e + format (PLabel props _ l) =+ indent 2 $ vcat [ text "label:" <+> format props, format l ] + format (PEllipse props _ e) = + indent 2 $ vcat [ text "ellipse:" <+> format props, format e ] -instance Pretty u => Pretty (Path u) where- pretty (Path pt ps) = pretty pt <> hcat (map pretty ps) -instance Pretty u => Pretty (PathSegment u) where- pretty (PCurveTo p1 p2 p3) = text ".*" <> pretty p1 <> text ",," <> pretty p2 - <> text "*." <> pretty p3- pretty (PLineTo pt) = text "--" <> pretty pt+instance PSUnit u => Format (PrimPath u) where+ format (PrimPath pt ps) = vcat (start : map format ps)+ where+ start = text "start_point " <> format pt -instance Pretty u => Pretty (Label u) where- pretty (Label pt s ctm) = dquotes (pretty s) <> char '@' <> pretty pt- <+> pretty ctm+instance PSUnit u => Format (PrimPathSegment u) where+ format (PCurveTo p1 p2 p3) =+ text "curve_to " <> format p1 <+> format p2 <+> format p3 -instance Pretty u => Pretty (PrimEllipse u) where- pretty (PrimEllipse ctr w h ctm) = pretty "ellipse" <+> pretty ctr- <+> text "w:" <> pretty w- <+> text "h:" <> pretty h- <+> pretty ctm+ format (PLineTo pt) = text "line_to " <> format pt++instance PSUnit u => Format (PrimLabel u) where+ format (PrimLabel pt s ctm) = + vcat [ dquotes (format s)+ , text "baseline_left=" <> format pt+ <+> text "ctm=" <> format ctm+ ]++instance PSUnit u => Format (PrimEllipse u) where+ format (PrimEllipse ctr hw hh ctm) = text "center=" <> format ctr+ <+> text "hw=" <> dtruncFmt hw+ <+> text "hh=" <> dtruncFmt hh+ <+> text "ctm=" <> format ctm -instance Pretty u => Pretty (PrimCTM u) where- pretty (PrimCTM x y ang) = - braces (pretty x <> comma <+> pretty y <> comma <+> pretty ang)+instance PSUnit u => Format (PrimCTM u) where+ format (PrimCTM x y ang) = + parens (text "CTM" <+> text "sx=" <> dtruncFmt x + <+> text "sy=" <> dtruncFmt y + <+> text "ang=" <> format ang ) ++instance Format PathProps where+ format (CFill rgb) = format rgb <+> text "Fill"+ format (CStroke _ rgb) = format rgb <+> text "Closed-stroke"+ format (OStroke _ rgb) = format rgb <+> text "Open-stroke"+ format (CFillStroke f _ s) = format f <+> text "Fill" <> char '/'+ <+> format s <+> text "Stroke" ++++instance Format LabelProps where+ format (LabelProps rgb attr) = format rgb + <+> text (font_name $ font_face attr)++instance Format EllipseProps where+ format (EFill rgb) = format rgb <+> text "Fill"+ format (EStroke _ rgb) = format rgb <+> text "Stroke"+ format (EFillStroke f _ s) = format f <+> text "Fill" <> char '/'+ <+> format s <+> text "Stroke" +++ -------------------------------------------------------------------------------- --- | Paths are sensibly a Semigroup - there is no notion of --- /empty path/.+instance Boundary (Picture u) where+ boundary (Leaf (bb,_) _) = bb+ boundary (Picture (bb,_) _) = bb+ boundary (Clip (bb,_) _ _) = bb+ boundary (Group (bb,_) _ _) = bb -instance Semigroup (Path u) where- Path st xs `append` Path st' xs' = Path st (xs ++ (PLineTo st' : xs'))+instance (Real u, Floating u, FromPtSize u) => Boundary (Primitive u) where+ boundary (PPath _ _ p) = pathBoundary p+ boundary (PLabel a _ l) = labelBoundary (label_font a) l+ boundary (PEllipse _ _ e) = ellipseBoundary e -instance Pointwise (Path u) where- type Pt (Path u) = Point2 u- pointwise f (Path st xs) = Path (f st) (map (pointwise f) xs) -instance Pointwise (PathSegment u) where- type Pt (PathSegment u) = Point2 u- pointwise f (PLineTo p) = PLineTo (f p)- pointwise f (PCurveTo p1 p2 p3) = PCurveTo (f p1) (f p2) (f p3)- +pathBoundary :: Ord u => PrimPath u -> BoundingBox u+pathBoundary (PrimPath st xs) = step (st,st) xs+ where+ step (lo,hi) [] = BBox lo hi + step (lo,hi) (PLineTo p1:rest) = step (lo2 lo p1, hi2 hi p1) rest+ step (lo,hi) (PCurveTo p1 p2 p3:rest) = let lo' = lo4 lo p1 p2 p3 + hi' = hi4 hi p1 p2 p3+ in step (lo',hi') rest + lo2 (P2 x1 y1) (P2 x2 y2) = P2 (min x1 x2) (min y1 y2) + hi2 (P2 x1 y1) (P2 x2 y2) = P2 (max x1 x2) (max y1 y2)++ lo4 (P2 x1 y1) (P2 x2 y2) (P2 x3 y3) (P2 x4 y4) = + P2 (min x1 $ min x2 $ min x3 x4) (min y1 $ min y2 $ min y3 y4) ++ hi4 (P2 x1 y1) (P2 x2 y2) (P2 x3 y3) (P2 x4 y4) = + P2 (max x1 $ max x2 $ max x3 x4) (max y1 $ max y2 $ max y3 y4) + +++labelBoundary :: (Floating u, Real u, FromPtSize u) + => FontAttr -> PrimLabel u -> BoundingBox u+labelBoundary attr (PrimLabel (P2 x y) xs ctm) = + retraceBoundary (disp . (m33 *#)) untraf_bbox+ where+ disp = (.+^ V2 x y)+ m33 = matrixRepCTM ctm+ untraf_bbox = textBounds (font_size attr) zeroPt char_count+ char_count = textLength xs++++-- | Ellipse bbox is the bounding rectangle, rotated as necessary +-- then retraced.+--+ellipseBoundary :: (Real u, Floating u) => PrimEllipse u -> BoundingBox u+ellipseBoundary (PrimEllipse pt hw0 hh0 (PrimCTM sx sy theta)) = + traceBoundary $ applyIf (theta /= 0) (map (rotm *#)) [ll,lr,ur,ul]+ where+ hw = hw0 * sx+ hh = hh0 * sy+ ll = pt .+^ V2 (-hw) (-hh) + lr = pt .+^ V2 hw (-hh) + ur = pt .+^ V2 hw hh + ul = pt .+^ V2 (-hw) hh + rotm = rotationMatrix theta+++ ----------------------------------------------------------------------------------- Affine trans instances+-- Affine transformations -type instance DUnit (Picture u) = u-type instance DUnit (Primitive u) = u-type instance DUnit (Path u) = u-type instance DUnit (PrimEllipse u) = u+-- Note YRange remains constant (as do the actually points +-- within the primitives).+-- +-- TO DO - this is potentially wrong... + instance (Num u, Ord u) => Transform (Picture u) where- transform ctm pic = trafoPicture (transform ctm) (transform ctm) pic+ transform mtrx = + mapLocale $ \(bb,xs) -> (transform mtrx bb, Matrix mtrx:xs) +instance (Real u, Floating u) => Rotate (Picture u) where+ rotate theta = + mapLocale $ \(bb,xs) -> (rotate theta bb, Rotate theta:xs) -instance (Floating u, Real u) => Rotate (Picture u) where- rotate = rotatePicture -instance (Floating u, Real u) => RotateAbout (Picture u) where- rotateAbout = rotatePictureAbout+instance (Real u, Floating u) => RotateAbout (Picture u) where+ rotateAbout theta pt = + mapLocale $ \(bb,xs) -> (rotateAbout theta pt bb, RotAbout theta pt:xs) instance (Num u, Ord u) => Scale (Picture u) where- scale = scalePicture+ scale sx sy = + mapLocale $ \(bb,xs) -> (scale sx sy bb, Scale sx sy : xs) instance (Num u, Ord u) => Translate (Picture u) where- translate = translatePicture+ translate dx dy = + mapLocale $ \(bb,xs) -> (translate dx dy bb, Translate dx dy:xs) +mapLocale :: (Locale u -> Locale u) -> Picture u -> Picture u+mapLocale f (Leaf lc ones) = Leaf (f lc) ones+mapLocale f (Picture lc ones) = Picture (f lc) ones+mapLocale f (Clip lc pp pic) = Clip (f lc) pp pic+mapLocale f (Group lc upd pic) = Group (f lc) upd pic +--------------------------------------------------------------------------------+-- Manipulating the PrimCTM --- Primitives+identityCTM :: Num u => PrimCTM u+identityCTM = PrimCTM { ctm_scale_x = 1, ctm_scale_y = 1, ctm_rotation = 0 } ++scaleCTM :: Num u => u -> u -> PrimCTM u -> PrimCTM u+scaleCTM x1 y1 (PrimCTM sx sy ang) = PrimCTM (x1*sx) (y1*sy) ang++rotateCTM :: Radian -> PrimCTM u -> PrimCTM u+rotateCTM ang1 (PrimCTM sx sy ang) = PrimCTM sx sy (circularModulo $ ang1+ang)++matrixRepCTM :: (Floating u, Real u) => PrimCTM u -> Matrix3'3 u+matrixRepCTM (PrimCTM sx sy ang) = + rotationMatrix (circularModulo ang) * scalingMatrix sx sy+++-- Note - the order of combining a translation (i.e. the +-- location of a point) and the CTM is crucial as matrix+-- multiplication is not commutative.+--+-- This function encapsulates the correct order.+--+translMatrixRepCTM :: (Floating u, Real u) + => u -> u -> PrimCTM u -> Matrix3'3 u+translMatrixRepCTM x y ctm = translationMatrix x y * matrixRepCTM ctm++++--------------------------------------------------------------------------------+-- Transform primitives++ -- | Rotate a Primitive. -- -- Note - this is not an affine transformation as Primitives are@@ -370,9 +551,9 @@ -- rotatePrimitive :: (Real u, Floating u) => Radian -> Primitive u -> Primitive u-rotatePrimitive ang (PPath a path) = PPath a $ rotatePath ang path-rotatePrimitive ang (PLabel a lbl) = PLabel a $ rotateLabel ang lbl-rotatePrimitive ang (PEllipse a ell) = PEllipse a $ rotateEllipse ang ell+rotatePrimitive ang (PPath a xl path) = PPath a xl $ rotatePath ang path+rotatePrimitive ang (PLabel a xl lbl) = PLabel a xl $ rotateLabel ang lbl+rotatePrimitive ang (PEllipse a xl ell) = PEllipse a xl $ rotateEllipse ang ell -- | Scale a Primitive.@@ -398,9 +579,9 @@ -- \"cost-free\". -- scalePrimitive :: Num u => u -> u -> Primitive u -> Primitive u-scalePrimitive x y (PPath a path) = PPath a $ scalePath x y path-scalePrimitive x y (PLabel a lbl) = PLabel a $ scaleLabel x y lbl-scalePrimitive x y (PEllipse a ell) = PEllipse a $ scaleEllipse x y ell+scalePrimitive x y (PPath a xl path) = PPath a xl $ scalePath x y path+scalePrimitive x y (PLabel a xl lbl) = PLabel a xl $ scaleLabel x y lbl+scalePrimitive x y (PEllipse a xl ell) = PEllipse a xl $ scaleEllipse x y ell -- | Apply a uniform scale to a Primitive. --@@ -418,61 +599,17 @@ -- the generated output. -- translatePrimitive :: Num u => u -> u -> Primitive u -> Primitive u-translatePrimitive x y (PPath a path) = PPath a $ translatePath x y path-translatePrimitive x y (PLabel a lbl) = PLabel a $ translateLabel x y lbl-translatePrimitive x y (PEllipse a ell) = PEllipse a $ translateEllipse x y ell--------------------------------------------------------------------------------------- Helpers for the affine transformations--rotatePicture :: (Real u, Floating u) => Radian -> Picture u -> Picture u-rotatePicture ang = trafoPicture (rotate ang) (rotate ang)--rotatePictureAbout :: (Real u, Floating u) - => Radian -> Point2 u -> Picture u -> Picture u-rotatePictureAbout ang pt = - trafoPicture (rotateAbout ang pt) (rotateAbout ang pt)- -scalePicture :: (Num u, Ord u) => u -> u -> Picture u -> Picture u-scalePicture x y = trafoPicture (scale x y) (scale x y)+translatePrimitive x y (PPath a xl path) = + PPath a xl $ translatePath x y path -translatePicture :: (Num u, Ord u) => u -> u -> Picture u -> Picture u-translatePicture x y = trafoPicture (translate x y) (translate x y)+translatePrimitive x y (PLabel a xl lbl) = + PLabel a xl $ translateLabel x y lbl --- TODO - the nameing for these functions is confusing now that--- I've added a Transform typeclass.------ Look to unifying the naming scheme in someway.----trafoPicture :: (Num u, Ord u) - => (Point2 u -> Point2 u) - -> (Vec2 u -> Vec2 u) - -> Picture u - -> Picture u-trafoPicture fp fv = - mapLocale $ \(frm,bb) -> (trafoFrame fp fv frm, trafoBBox fp bb)+translatePrimitive x y (PEllipse a xl ell) = + PEllipse a xl $ translateEllipse x y ell -trafoFrame :: Num u- => (Point2 u -> Point2 u) - -> (Vec2 u -> Vec2 u) - -> Frame2 u - -> Frame2 u-trafoFrame fp fv (Frame2 e0 e1 o) = Frame2 (fv e0) (fv e1) (fp o)----- Bounding boxes need recalculating after a transformation.--- For instance after a reflection in the y-axis br becomes bl.-trafoBBox :: (Num u, Ord u)- => (Point2 u -> Point2 u) -> BoundingBox u -> BoundingBox u-trafoBBox fp bb = traceBoundary $ map fp $ [bl,br,tl,tr]- where- (bl,br,tr,tl) = corners bb-- -------------------------------------------------------------------------------- -- Paths @@ -487,9 +624,10 @@ -- (0,0), but don\'t not basis vectors) so manipulating them -- cannot follow the standard affine interpretation. -- -rotatePath :: (Real u, Floating u) => Radian -> Path u -> Path u-rotatePath ang (Path start xs) = - Path start $ map (pointwise (rotateAbout ang start)) xs+rotatePath :: (Real u, Floating u) => Radian -> PrimPath u -> PrimPath u+rotatePath ang (PrimPath start xs) = PrimPath start $ map (mapSeg fn) xs + where+ fn = rotateAbout ang start -- scalePath - scale the vector between each point and the start -- point.@@ -499,45 +637,24 @@ -- are lifted to Pictures their manipulation cannot correspond -- to the standard affine manipulations. ---scalePath :: Num u => u -> u -> Path u -> Path u-scalePath x y (Path pt xs) = Path pt (map (pointwise fn) xs) +scalePath :: Num u => u -> u -> PrimPath u -> PrimPath u+scalePath x y (PrimPath pt xs) = PrimPath pt $ map (mapSeg fn) xs where fn p1 = let dif = p1 .-. pt in pt .+^ (scale x y $ dif) -- translatePath - move all points in the path by the supplied -- x and y values. ---translatePath :: Num u => u -> u -> Path u -> Path u-translatePath x y = pointwise (translate x y)-+translatePath :: Num u => u -> u -> PrimPath u -> PrimPath u+translatePath x y = mapPath (translate x y) ------------------------------------------------------------------------------------ Manipulating the Primitive CTM--identityCTM :: Num u => PrimCTM u-identityCTM = PrimCTM { ctm_scale_x = 1, ctm_scale_y = 1, ctm_rotation = 0 }--scaleCTM :: Num u => u -> u -> PrimCTM u -> PrimCTM u-scaleCTM x1 y1 (PrimCTM x y ang) = PrimCTM (x1*x) (y1*y) ang--rotateCTM :: Radian -> PrimCTM u -> PrimCTM u-rotateCTM ang1 (PrimCTM x y ang) = PrimCTM x y (circularModulo $ ang1+ang)--matrixRepCTM :: (Floating u, Real u) => PrimCTM u -> Matrix3'3 u-matrixRepCTM (PrimCTM x y ang) = - rotationMatrix (circularModulo ang) * scalingMatrix x y-+mapPath :: (Point2 u -> Point2 u) -> PrimPath u -> PrimPath u+mapPath fn (PrimPath st xs) = PrimPath (fn st) (map (mapSeg fn) xs) --- Note - the order of combining a translation (i.e. the --- location of a point) and the CTM is crucial as matrix--- multiplication is not commutative.------ The function encapsulated the correct order.----translMatrixRepCTM :: (Floating u, Real u) - => u -> u -> PrimCTM u -> Matrix3'3 u-translMatrixRepCTM x y ctm = translationMatrix x y * matrixRepCTM ctm+mapSeg :: (Point2 u -> Point2 u) -> PrimPathSegment u -> PrimPathSegment u+mapSeg fn (PLineTo p) = PLineTo (fn p)+mapSeg fn (PCurveTo p1 p2 p3) = PCurveTo (fn p1) (fn p2) (fn p3) -------------------------------------------------------------------------------- -- Labels@@ -547,17 +664,17 @@ -- Rotations on a (primitive) Label are interpreted as rotating -- about the bottom-left corner. ---rotateLabel :: Radian -> Label u -> Label u-rotateLabel ang (Label pt txt ctm) = Label pt txt (rotateCTM ang ctm)+rotateLabel :: Radian -> PrimLabel u -> PrimLabel u+rotateLabel ang (PrimLabel pt txt ctm) = PrimLabel pt txt (rotateCTM ang ctm) -scaleLabel :: Num u => u -> u -> Label u -> Label u-scaleLabel x y (Label pt txt ctm) = Label pt txt (scaleCTM x y ctm)+scaleLabel :: Num u => u -> u -> PrimLabel u -> PrimLabel u+scaleLabel x y (PrimLabel pt txt ctm) = PrimLabel pt txt (scaleCTM x y ctm) -- Change the bottom-left corner. ---translateLabel :: Num u => u -> u -> Label u -> Label u-translateLabel x y (Label pt txt ctm) = Label (translate x y pt) txt ctm+translateLabel :: Num u => u -> u -> PrimLabel u -> PrimLabel u+translateLabel x y (PrimLabel pt txt ctm) = PrimLabel (translate x y pt) txt ctm -------------------------------------------------------------------------------- -- Ellipse@@ -580,153 +697,56 @@ translateEllipse :: Num u => u -> u -> PrimEllipse u -> PrimEllipse u translateEllipse x y (PrimEllipse pt hw hh ctm) = PrimEllipse (translate x y pt) hw hh ctm- ------------------------------------------------------------------------------------ Boundary -instance Boundary (Picture u) where- boundary (PicBlank (_,bb)) = bb- boundary (Single (_,bb) _) = bb- boundary (Picture (_,bb) _) = bb- boundary (Clip (_,bb) _ _) = bb -instance (Num u, Ord u) => Boundary (Path u) where- boundary (Path st xs) = traceBoundary $ st : foldr f [] xs where- f (PLineTo p1) acc = p1 : acc- f (PCurveTo p1 p2 p3) acc = p1 : p2 : p3 : acc +--------------------------------------------------------------------------------+-- Additional operations --- Note - this will calculate an approximate bounding box for --- text.--instance (Real u, Floating u, FromPtSize u) => Boundary (Primitive u) where- boundary (PPath _ p) = boundary p- boundary (PLabel (_,a) l) = primLabelBoundary a l - boundary (PEllipse _ e) = boundary e+concatTrafos :: (Floating u, Real u) => [AffineTrafo u] -> Matrix3'3 u+concatTrafos = foldr (\e ac -> matrixRepr e * ac) identityMatrix +matrixRepr :: (Floating u, Real u) => AffineTrafo u -> Matrix3'3 u+matrixRepr (Matrix mtrx) = mtrx+matrixRepr (Rotate theta) = rotationMatrix theta+matrixRepr (RotAbout theta pt) = originatedRotationMatrix theta pt+matrixRepr (Scale sx sy) = scalingMatrix sx sy +matrixRepr (Translate dx dy) = translationMatrix dx dy -primLabelBoundary :: (Floating u, Real u, FromPtSize u) - => FontAttr -> Label u -> BoundingBox u-primLabelBoundary attr (Label (P2 x y) xs ctm) = - retraceBoundary (disp . (m33 *#)) untraf_bbox- where- disp = (.+^ V2 x y)- m33 = matrixRepCTM ctm- untraf_bbox = textBounds (font_size attr) zeroPt char_count- char_count = textLength xs--instance (Real u, Floating u) => Boundary (PrimEllipse u) where- boundary = ellipseBoundary---- Find the bbox of an ellipse by drawing it as four bezier --- curves then trace all the points and control points to find--- the bbox.--- --- Note all_points takes three of the four points to avoid --- duplicating--- /matched/ start-end points-----ellipseBoundary :: (Real u, Floating u) => PrimEllipse u -> BoundingBox u-ellipseBoundary = traceBoundary . ellipseControlPoints---- PROBLEM:--- Currently a rotated circle has a different BBox to a --- non-rotated circle, because of how tangents are selected...+-- | Destructor for Matrix3'3. -- --- This is the same as a diamond having a larger BBox--- than a square with same side-length-------------------------------------------------------------------------------------+-- Pattern matching on 6-tuple may be more convenient than using +-- the Matrix3'3 directly. ---+-- > (M3'3 e0x e1x ox +-- > e0y e1y oy +-- > _ _ _ ) = (e0x,e0y, e1x,e1y, ox,oy)+-- +deconsMatrix :: Matrix3'3 u -> (u,u,u,u,u,u)+deconsMatrix (M3'3 e0x e1x ox + e0y e1y oy + _ _ _ ) = (e0x,e0y, e1x,e1y, ox,oy) -mapLocale :: (Locale u -> Locale u) -> Picture u -> Picture u-mapLocale f (PicBlank m) = PicBlank (f m)-mapLocale f (Single m prim) = Single (f m) prim-mapLocale f (Picture m ones) = Picture (f m) ones-mapLocale f (Clip m x p) = Clip (f m) x p---movePic :: Num u => Vec2 u -> Picture u -> Picture u-movePic v = mapLocale (moveLocale v) -- -moveLocale :: Num u => Vec2 u -> Locale u -> Locale u-moveLocale v (fr,bb) = (displaceOrigin v fr, pointwise (.+^ v) bb) --------------------------------------------------------------------------------------- | Should this really be public?-extractFrame :: Num u => Picture u -> Frame2 u-extractFrame (PicBlank (fr,_)) = fr-extractFrame (Single (fr,_) _) = fr-extractFrame (Picture (fr,_) _) = fr-extractFrame (Clip (fr,_) _ _) = fr----- This needs is for PostScript and SVG output - it should be --- hidden in the export list of Wumpus.Core-- -- If a picture has coordinates smaller than (P2 4 4) then it --- needs repositioning before it is drawn to PostSCript or SVG.+-- needs repositioning before it is drawn to PostScript or SVG. -- -- (P2 4 4) gives a 4 pt margin - maybe it sould be (0,0) or -- user defined. ---repositionProperties :: (Num u, Ord u) - => Picture u -> (BoundingBox u, Maybe (Vec2 u))-repositionProperties = fn . boundary where- fn bb@(BBox (P2 llx lly) (P2 urx ury))- | llx < 4 || lly < 4 = (BBox ll ur, Just $ V2 x y)- | otherwise = (bb, Nothing)- where - x = 4 - llx- y = 4 - lly- ll = P2 (llx+x) (lly+y)- ur = P2 (urx+x) (ury+y) ----- | Get the control points as a list--- --- There are no duplicates in the list except for the final --- /wrap-around/. We take 4 points initially (start,cp1,cp2,end)--- then (cp1,cp2,end) for the other three quadrants.----ellipseControlPoints :: (Floating u, Real u)- => PrimEllipse u -> [Point2 u]-ellipseControlPoints (PrimEllipse (P2 x y) hw hh ctm) = - map (disp . (new_mtrx *#)) circ- where- disp = (.+^ V2 x y)- (radius,(dx,dy)) = circleScalingProps hw hh- new_mtrx = matrixRepCTM $ scaleCTM dx dy ctm- circ = bezierCircle 1 radius (P2 0 0)-- -- subdivide the bezierCircle with 1 to get two- -- control points per quadrant. -------- I don't know how to calculate bezier arcs (and thus control--- points) for an ellipse but I know how to do it for a circle...------ So a make a circle with the largest of half-width and --- half-height then apply a scale to the points--- -circleScalingProps :: (Fractional u, Ord u) => u -> u -> (u,(u,u))-circleScalingProps hw hh = (radius, (dx,dy))+repositionDeltas :: (Num u, Ord u) + => Picture u -> (BoundingBox u, Maybe (Vec2 u))+repositionDeltas = step . boundary where- radius = max hw hh- (dx,dy) = if radius == hw then (1, rescale (0,hw) (0,1) hh)- else (rescale (0,hh) (0,1) hw, 1)--+ step bb@(BBox (P2 llx lly) (P2 urx ury))+ | llx < 4 || lly < 4 = (BBox ll ur, Just $ V2 x y)+ | otherwise = (bb, Nothing)+ where + x = 4 - llx+ y = 4 - lly+ ll = P2 (llx+x) (lly+y)+ ur = P2 (urx+x) (ury+y)
− src/Wumpus/Core/PostScript.hs
@@ -1,544 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# OPTIONS -Wall #-}------------------------------------------------------------------------------------- |--- Module : Wumpus.Core.PostScript--- Copyright : (c) Stephen Tetley 2009-2010--- License : BSD3------ Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more------ PostScript generation via a Writer monad.------ PostScript is emitted line by line - there is no abstract--- syntax tree representing PostScript. So we use a writer --- monad.----------------------------------------------------------------------------------------module Wumpus.Core.PostScript - (- -- * Types- PostScript- , WumpusM-- , runWumpus-- -- * Escape sepcial characters- , escapeStringPS-- -- * Deltas - , deltaFontAttr- , deltaRgbColour-- , deltaStrokeWidth- , deltaMiterLimit- , deltaLineCap- , deltaLineJoin- , deltaDashPattern- - -- * Emit PostScript - , ps_comment- - , ps_gsave- , ps_grestore- , ps_setlinewidth- , ps_setlinecap- , ps_setlinejoin- , ps_setmiterlimit- , ps_setdash- , ps_setgray- , ps_setrgbcolor- , ps_sethsbcolor- , ps_translate- , ps_scale- , ps_concat- , ps_newpath- , ps_moveto- , ps_rmoveto- , ps_lineto- , ps_rlineto- , ps_arc- , ps_arcn- , ps_curveto- , ps_closepath- , ps_clip- , ps_fill- , ps_stroke- , ps_showpage- , ps_findfont- , ps_scalefont- , ps_setfont- , ps_show- , ps_glyphshow- , bang_PS- , bang_EPS- , dsc_comment- , dsc_BoundingBox- , dsc_CreationDate- , dsc_Pages- , dsc_Page- , dsc_EndComments- , dsc_EOF-- ) where--import Wumpus.Core.Colour-import Wumpus.Core.GraphicsState-import Wumpus.Core.TextEncoder-import Wumpus.Core.Utils---import MonadLib--import Data.List ( foldl' )----- Graphics state for PostScript Rendering------ Values with no default value (e.g. font) in the graphics --- state of the PostScript interpreter (not Wumpus\'s renderer) --- are Maybes.--- --- The graphics state is considered successive - all elements --- have a colour and all text labels have a font. So during --- processing there are two situations: ---- (1) If font or colour is the same as the last no state--- change needs to be printed.------ (2) If the font or colour changes the update needs to be --- printed, but as the next element always has a colour (and a--- font if it is a label), no @undo@ needs to be printed.------ This contrasts with the behaviour for stroke attributes--- which needs @undo@.--data PostScriptGS = PostScriptGS { - gs_font :: Maybe FontAttr,- gs_rgb_colour :: DRGB- }- deriving (Eq,Show)------ | Stroke properties do not have to be fully specified in --- Wumpus\'s picture types - i.e. a Path might have it\'s stroke --- width set but nothing else.------ If a path changes any of the stroke properties, it --- immediately undoes the changes after drawing, returning the --- the stroke values to their PostScript defaults.------ This means Wumpus doesn't have to carry a nested environment --- around as it renders to PostScript. As stroke properties can--- only be assigned to leaves in the picture tree, a nested --- environment wouldn\'t really be an ideal fit anyway.---gs_stroke_width :: Double-gs_stroke_width = 1.0--gs_miter_limit :: Double-gs_miter_limit = 10.0--gs_line_cap :: LineCap-gs_line_cap = CapSquare--gs_line_join :: LineJoin-gs_line_join = JoinMiter--gs_dash_pattern :: DashPattern-gs_dash_pattern = Solid----type PostScript = String--type PsOutput = H Char--type WumpusM a = PsT Id a---newtype PsT m a = PsT { - unPsT :: StateT PostScriptGS - (WriterT PsOutput (ReaderT TextEncoder m)) a }--gs_init :: PostScriptGS -gs_init = PostScriptGS { gs_font = Nothing- , gs_rgb_colour = black - }- - -runPsT :: Monad m - => TextEncoder -> PsT m a -> m ((a,PostScriptGS),PsOutput)-runPsT i m = runReaderT i $ runWriterT $ runStateT gs_init $ unPsT m--instance Monad m => Functor (PsT m) where- fmap f (PsT mf) = PsT $ fmap f mf --instance Monad m => Monad (PsT m) where- return a = PsT $ return a- ma >>= f = PsT $ unPsT ma >>= unPsT . f--instance Monad m => WriterM (PsT m) PsOutput where- put = PsT . put--instance Monad m => ReaderM (PsT m) TextEncoder where- ask = PsT $ ask--instance Monad m => StateM (PsT m) PostScriptGS where- set = PsT . set- get = PsT $ get--instance MonadT PsT where- lift = PsT . lift . lift . lift---pstId :: TextEncoder -> PsT Id a -> ((a,PostScriptGS),PsOutput)-pstId = runId `oo` runPsT---- | Drop state and result, take the Writer trace.-runWumpus :: TextEncoder -> WumpusM a -> String-runWumpus = (toListH . snd) `oo` pstId------------------------------------------------------------------------------------- Escape special chars---- | Escape these characters:------ > \\ - (, ), <, >, [, ], {, }, /, and %----escapeStringPS :: String -> String-escapeStringPS = foldr f "" where- f c ss | c `elem` ps_special = '\\' : c : ss- | otherwise = c : ss--ps_special :: [Char]-ps_special = "\\()<>[]{}/%"-------------------------------------------------------------------------------------- "Deltas" of the graphics state--deltaFontAttr :: FontAttr -> WumpusM (Maybe FontAttr)-deltaFontAttr new = get >>= maybe update diff . gs_font- where- update :: WumpusM (Maybe FontAttr)- update = sets_ (\s -> s { gs_font = Just new }) >> return (Just new)- - diff :: FontAttr -> WumpusM (Maybe FontAttr)- diff old | old == new = return Nothing- | otherwise = update---deltaRgbColour :: DRGB -> WumpusM (Maybe DRGB)-deltaRgbColour new = get >>= diff . gs_rgb_colour- where- diff :: DRGB -> WumpusM (Maybe DRGB)- diff old | old == new = return Nothing- | otherwise = do { sets_ (\s -> s { gs_rgb_colour = new })- ; return (Just new)- }---deltaStrokeWidth :: Double -> Maybe (Double,Double)-deltaStrokeWidth n- | n == gs_stroke_width = Nothing- | otherwise = Just (n,gs_stroke_width)--deltaMiterLimit :: Double -> Maybe (Double,Double)-deltaMiterLimit n - | n == gs_miter_limit = Nothing- | otherwise = Just (n,gs_miter_limit)---deltaLineCap :: LineCap -> Maybe (LineCap,LineCap)-deltaLineCap lc- | lc == gs_line_cap = Nothing- | otherwise = Just (lc,gs_line_cap)--deltaLineJoin :: LineJoin -> Maybe (LineJoin,LineJoin)-deltaLineJoin lj - | lj == gs_line_join = Nothing- | otherwise = Just (lj,gs_line_join)--deltaDashPattern :: DashPattern -> Maybe (DashPattern,DashPattern)-deltaDashPattern p - | p == gs_dash_pattern = Nothing- | otherwise = Just (p,gs_dash_pattern)--------------------------------------------------------------------------------------- writer monad helpers--tell :: WriterM m i => i -> m ()-tell s = puts ((),s)--writeChar :: WriterM m PsOutput => Char -> m ()-writeChar = tell . showChar ---write :: WriterM m PsOutput => String -> m ()-write = tell . showString ---writeln :: WriterM m PsOutput => String -> m ()-writeln s = write s >> writeChar '\n'---writeArg :: WriterM m PsOutput => String -> m () -writeArg s = write s >> writeChar ' '-----type Command = String--command :: Command -> [String] -> WumpusM ()-command cmd xs = mapM_ writeArg xs >> writeln cmd----showArray :: (a -> ShowS) -> [a] -> String-showArray _ [] = "[ ]"-showArray f (x:xs) = sfun "]" - where - sfun = foldl' (\a e -> a . (' ':) . f e) (('[':) . f x) xs- ----- | @ %% ... @-ps_comment :: String -> WumpusM ()-ps_comment s = write "%% " >> writeln s------------------------------------------------------------------------------------- graphics state operators---- | @ gsave @-ps_gsave :: WumpusM ()-ps_gsave = command "gsave" []---- | @ grestore @-ps_grestore :: WumpusM () -ps_grestore = command "grestore" []---- | @ ... setlinewidth @-ps_setlinewidth :: PSUnit u => u -> WumpusM ()-ps_setlinewidth = command "setlinewidth" . return . dtrunc---- | @ ... setlinecap @-ps_setlinecap :: LineCap -> WumpusM ()-ps_setlinecap = command "setlinecap" . return . show . fromEnum---- | @ ... setlinejoin @-ps_setlinejoin :: LineJoin -> WumpusM ()-ps_setlinejoin = command "setlinejoin" . return . show . fromEnum---- | @ ... setmiterlimit @-ps_setmiterlimit :: PSUnit u => u -> WumpusM ()-ps_setmiterlimit = command "setmiterlimit" . return . dtrunc---- | @ [... ...] ... setdash @-ps_setdash :: DashPattern -> WumpusM ()-ps_setdash Solid = command "setdash" ["[]", "0"]-ps_setdash (Dash n pairs) = command "setdash" [showArray shows arr, show n]- where- arr = foldr (\(x,y) a -> x:y:a) [] pairs---- | @ ... setgray @-ps_setgray :: PSUnit u => u -> WumpusM ()-ps_setgray = command "setgray" . return . dtrunc ---- | @ ... ... ... setrgbcolor @-ps_setrgbcolor :: PSUnit u => u -> u -> u -> WumpusM ()-ps_setrgbcolor r g b = command "setrgbcolor" $ map dtrunc [r,g,b]---- | @ ... ... ... sethsbcolor @-ps_sethsbcolor :: PSUnit u => u -> u -> u -> WumpusM ()-ps_sethsbcolor h s b = command "sethsbcolor" $ map dtrunc [h,s,b]-------------------------------------------------------------------------------------- coordinate system and matrix operators ---- | @ ... ... translate @-ps_translate :: PSUnit u => u -> u -> WumpusM ()-ps_translate tx ty = do- command "translate" $ map dtrunc [tx,ty]---- | @ ... ... scale @-ps_scale :: PSUnit u => u -> u -> WumpusM ()-ps_scale tx ty = do- command "scale" $ map dtrunc [tx,ty]----- Do not use setmatrix for changing the CTM use concat...---- | @ [... ... ... ... ... ...] concat @-ps_concat :: PSUnit u => CTM u -> WumpusM ()-ps_concat (CTM a b c d e f) = command "concat" [mat] where - mat = showArray ((++) . dtrunc) [a,b,c,d,e,f]-------------------------------------------------------------------------------------- Path construction operators---- | @ newpath @-ps_newpath :: WumpusM ()-ps_newpath = command "newpath" []----- Note - it is preferable to show doubles as 0.0 rather than 0.--- In PostScript the coercion from int to float is apparently --- quite expensive.---- | @ ... ... moveto @-ps_moveto :: PSUnit u => u -> u -> WumpusM ()-ps_moveto x y = command "moveto" [dtrunc x, dtrunc y]---- | @ ... ... rmoveto @-ps_rmoveto :: PSUnit u => u -> u -> WumpusM ()-ps_rmoveto x y = command "rmoveto" [dtrunc x, dtrunc y]---- | @ ... ... lineto @-ps_lineto :: PSUnit u => u -> u -> WumpusM ()-ps_lineto x y = command "lineto" [dtrunc x, dtrunc y]---- | @ ... ... rlineto @-ps_rlineto :: PSUnit u => u -> u -> WumpusM ()-ps_rlineto x y = command "rlineto" [dtrunc x, dtrunc y]---- | @ ... ... ... ... ... arc @-ps_arc :: PSUnit u => u -> u -> u -> u -> u -> WumpusM ()-ps_arc x y r ang1 ang2 = - command "arc" $ map dtrunc [x,y,r,ang1,ang2]---- | @ ... ... ... ... ... arcn @-ps_arcn :: PSUnit u => u -> u -> u -> u -> u -> WumpusM ()-ps_arcn x y r ang1 ang2 = - command "arcn" $ map dtrunc [x,y,r,ang1,ang2]---- | @ ... ... ... ... ... ... curveto @-ps_curveto :: PSUnit u => u -> u -> u -> u -> u -> u -> WumpusM ()-ps_curveto x1 y1 x2 y2 x3 y3 = - command "curveto" $ map dtrunc [x1,y1, x2,y2, x3,y3]---- | @ closepath @-ps_closepath :: WumpusM ()-ps_closepath = command "closepath" []---- | @ clip @-ps_clip :: WumpusM ()-ps_clip = command "clip" []------------------------------------------------------------------------------------- painting operators---- | @ fill @-ps_fill :: WumpusM ()-ps_fill = command "fill" []---- | @ stroke @-ps_stroke :: WumpusM ()-ps_stroke = command "stroke" []-------------------------------------------------------------------------------------- Output operators---- | @ showpage @-ps_showpage :: WumpusM ()-ps_showpage = command "showpage" []--------------------------------------------------------------------------------------- Character and font operators---- | The following fonts are expected to exist on most platforms:------ > Times-Roman Times-Italic Times-Bold Times-BoldItalic--- > Helvetica Helvetica-Oblique Helvetica-Bold Helvetica-Bold-Oblique--- > Courier Courier-Oblique Courier-Bold Courier-Bold-Oblique--- > Symbol------ List from Bill Casselman \'Mathematical Illustrations\' p279.---- | @ /... findfont @-ps_findfont :: String -> WumpusM () -ps_findfont = command "findfont" . return . ('/' :)---- | @ ... scalefont @-ps_scalefont :: Int -> WumpusM ()-ps_scalefont = command "scalefont" . return . show---- | @ setfont @-ps_setfont :: WumpusM ()-ps_setfont = command "setfont" []---- | @ (...) show @-ps_show :: String -> WumpusM ()-ps_show = command "show" . return . parens---- | @ (...) show @-ps_glyphshow :: String -> WumpusM ()-ps_glyphshow = command "glyphshow" . return . ('/':)-------------------------------------------------------------------------------------- document structuring conventions---- | @ %!PS-Adobe-3.0 @-bang_PS :: WumpusM ()-bang_PS = writeln "%!PS-Adobe-3.0"---- | @ %!PS-Adobe-3.0 EPSF-3.0 @-bang_EPS :: WumpusM ()-bang_EPS = writeln "%!PS-Adobe-3.0 EPSF-3.0"---- | @ %%...: ... @-dsc_comment :: String -> [String] -> WumpusM ()-dsc_comment name [] = write "%%" >> writeln name-dsc_comment name xs = write "%%" >> write name >> write ": " >> writeln (hsep xs)----- | @ %%BoundingBox: ... ... ... ... @ /llx lly urx ury/-dsc_BoundingBox :: PSUnit u => u -> u -> u -> u -> WumpusM ()-dsc_BoundingBox llx lly urx ury = - dsc_comment "BoundingBox" (map (roundup . toDouble) [llx,lly,urx,ury])---- | @ %%CreationDate: ... @--- --- The creation date is informational and never interpreted, --- thus the format is entirely arbitrary.-dsc_CreationDate :: String -> WumpusM ()-dsc_CreationDate = dsc_comment "CreationDate" . return---- | @ %%Pages: ... @-dsc_Pages :: Int -> WumpusM ()-dsc_Pages = dsc_comment "Pages" . return . show----- | @ %%Page: ... ... @-dsc_Page :: String -> Int -> WumpusM ()-dsc_Page label ordinal = - dsc_comment "Page" [label, show ordinal]----- | @ %%EndComments @-dsc_EndComments :: WumpusM ()-dsc_EndComments = dsc_comment "EndComments" []---- | @ %%EOF @-dsc_EOF :: WumpusM ()-dsc_EOF = dsc_comment "EOF" []-
+ src/Wumpus/Core/PostScriptDoc.hs view
@@ -0,0 +1,350 @@+{-# OPTIONS -Wall #-}++--------------------------------------------------------------------------------+-- |+-- Module : Wumpus.Core.PostScriptDoc+-- Copyright : (c) Stephen Tetley 2009-2010+-- License : BSD3+--+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC+--+-- PostScript Doc combinators.+--+--------------------------------------------------------------------------------++module Wumpus.Core.PostScriptDoc+ ( + + escapeSpecial+ + , psHeader+ , epsHeader+ , psFooter+ , epsFooter+ , dsc_Page++ , missingCharCode++ , command+ , ps_comment++ , ps_gsave+ , ps_grestore+ , ps_setlinewidth+ , ps_setlinecap+ , ps_setlinejoin+ , ps_setmiterlimit+ , ps_setdash+ , ps_setrgbcolor++ , ps_translate+ , ps_concat++ , ps_newpath+ , ps_moveto+ , ps_lineto+ , ps_arc+ , ps_curveto+ , ps_closepath+ , ps_clip++ , ps_fill + , ps_stroke+ + , ps_showpage+ , ps_findfont+ , ps_scalefont+ , ps_setfont+ , ps_show+ , ps_glyphshow++ ) where++import Wumpus.Core.BoundingBox+import Wumpus.Core.Colour+import Wumpus.Core.FormatCombinators+import Wumpus.Core.Geometry+import Wumpus.Core.GraphicsState+import Wumpus.Core.PictureInternal+import Wumpus.Core.TextEncoder+import Wumpus.Core.Utils++import Data.Time+++++--------------------------------------------------------------------------------+-- Escape special chars++-- | Escape these characters:+--+-- > \\ - (, ), <, >, [, ], {, }, /, and %+--+escapeSpecial :: String -> String+escapeSpecial = foldr f "" + where+ f c ss | c `elem` ps_special = '\\' : c : ss+ | otherwise = c : ss++ps_special :: [Char]+ps_special = "\\()<>[]{}/%"++++psHeader :: Int -> ZonedTime -> Doc+psHeader page_count tod = vcat $ + [ text "%!PS-Adobe-3.0"+ , text "%%Pages:" <+> int page_count+ , text "%%CreationDate:" <+> parens (showsDoc $ psTimeStamp tod) + , text "%%EndComments" + ]+++epsHeader :: PSUnit u => BoundingBox u -> ZonedTime -> Doc+epsHeader bb tod = vcat $ + [ text "%!PS-Adobe-3.0 EPSF-3.0"+ , text "%%BoundingBox:" <+> upint llx <+> upint lly+ <+> upint urx <+> upint ury+ , text "%%CreationDate:" <+> parens (showsDoc $ psTimeStamp tod) + , text "%%EndComments" + ]+ where+ upint = text . roundup . toDouble+ (llx,lly,urx,ury) = destBoundingBox bb ++++psFooter :: Doc+psFooter = text "%%EOF"+++epsFooter :: Doc+epsFooter = vcat [ text "showpage", text "%%EOF" ]+++-- | @ %%Page: ... ... @+--+dsc_Page :: String -> Int -> Doc+dsc_Page label ordinal = text "%%Page:" <+> text label <+> int ordinal+++missingCharCode :: CharCode -> GlyphName -> Doc+missingCharCode i fallback = vcat $+ [ ps_comment $ "missing lookup for &#" ++ show i ++ ";" + , ps_glyphshow fallback+ ]++--------------------------------------------------------------------------------++formatArray :: (a -> Doc) -> [a] -> Doc+formatArray _ [] = brackets space+formatArray fn xs = brackets (separate space $ map fn xs)++command :: String -> [Doc] -> Doc+command cmd [] = text cmd+command cmd ds = hsep ds <+> text cmd +++-- | @ %% ... @+--+ps_comment :: String -> Doc+ps_comment ss = text "%%" <+> text ss++--------------------------------------------------------------------------------+-- Graphics state operators++-- | @ gsave @+--+ps_gsave :: Doc+ps_gsave = command "gsave" []++-- | @ grestore @+--+ps_grestore :: Doc+ps_grestore = command "grestore" []++++-- | @ ... setlinewidth @+--+ps_setlinewidth :: PSUnit u => u -> Doc+ps_setlinewidth u = command "setlinewidth" [dtruncFmt u]++-- | @ ... setlinecap @+--+ps_setlinecap :: LineCap -> Doc+ps_setlinecap a = command "setlinecap" [int $ fromEnum a]++-- | @ ... setlinejoin @+--+ps_setlinejoin :: LineJoin -> Doc+ps_setlinejoin a = command "setlinejoin" [int $ fromEnum a]++-- | @ ... setmiterlimit @+--+ps_setmiterlimit :: PSUnit u => u -> Doc+ps_setmiterlimit u = command "setmiterlimit" [dtruncFmt u]++-- | @ [... ...] ... setdash @+--+ps_setdash :: DashPattern -> Doc+ps_setdash Solid = command "setdash" [text "[]", char '0']+ps_setdash (Dash n pairs) = command "setdash" [brackets $ step pairs, int n]+ where+ step [] = empty+ step [(a,b)] = int a <+> int b+ step ((a,b):xs) = int a <+> int b <+> step xs ++-- | @ ... ... ... setrgbcolor @+--+ps_setrgbcolor :: RGBi -> Doc+ps_setrgbcolor (RGBi r g b) = command "setrgbcolor" [fn r, fn g, fn b]+ where+ fn i = dtruncFmt $ (fromIntegral i / d255)+ d255 :: Double+ d255 = 255.0++--------------------------------------------------------------------------------+-- coordinate system and matrix operators ++-- | @ ... ... translate @+ps_translate :: PSUnit u => (Vec2 u) -> Doc+ps_translate (V2 dx dy) = command "translate" [dtruncFmt dx, dtruncFmt dy]+++-- Note - Do not use @setmatrix@ for changing the CTM use +-- @concat@ instead.+--+-- Using concat is harmonious with /nesting/.+-- ++-- | @ [... ... ... ... ... ...] concat @+--+ps_concat :: PSUnit u => Matrix3'3 u -> Doc+ps_concat mtrx = doc <+> text "concat"+ where + (a,b,c,d,e,f) = deconsMatrix mtrx+ doc = formatArray dtruncFmt [a,b,c,d,e,f]++--------------------------------------------------------------------------------+-- Path construction operators++-- | @ newpath @+--+ps_newpath :: Doc+ps_newpath = command "newpath" []++-- Note - it is apparently preferable to show doubles as 0.0 +-- rather than 0.+--+-- I have read that in PostScript the coercion from int to float+-- is apparently quite expensive (I ought to locate the reference +-- for this...).++-- | @ ... ... moveto @+--+ps_moveto :: PSUnit u => Point2 u -> Doc+ps_moveto (P2 x y) = command "moveto" [dtruncFmt x, dtruncFmt y]+++-- | @ ... ... lineto @+--+ps_lineto :: PSUnit u => Point2 u -> Doc+ps_lineto (P2 x y) = command "lineto" [dtruncFmt x, dtruncFmt y]+++-- | @ ... ... ... ... ... arc @+--+ps_arc :: PSUnit u => Point2 u -> u -> Radian -> Radian -> Doc+ps_arc (P2 x y) radius ang1 ang2 = + command "arc" $ [ dtruncFmt x+ , dtruncFmt y+ , dtruncFmt radius+ , dtruncFmt $ fromR ang1+ , dtruncFmt $ fromR ang2+ ]+ where+ fromR :: Radian -> Double+ fromR = fromRadian++-- | @ ... ... ... ... ... ... curveto @+--+ps_curveto :: PSUnit u => Point2 u -> Point2 u -> Point2 u -> Doc+ps_curveto (P2 x1 y1) (P2 x2 y2) (P2 x3 y3) =+ command "curveto" $ map dtruncFmt [x1,y1, x2,y2, x3,y3]++-- | @ closepath @+--+ps_closepath :: Doc+ps_closepath = command "closepath" []+++-- | @ clip @+--+ps_clip :: Doc+ps_clip = command "clip" []+++--------------------------------------------------------------------------------+-- painting operators++-- | @ fill @+--+ps_fill :: Doc+ps_fill = command "fill" []++-- | @ stroke @+--+ps_stroke :: Doc+ps_stroke = command "stroke" []++--------------------------------------------------------------------------------+-- Output operators++-- | @ showpage @+ps_showpage :: Doc+ps_showpage = command "showpage" []+++--------------------------------------------------------------------------------+-- Character and font operators++-- | The following fonts are expected to exist on most platforms:+--+-- > Times-Roman Times-Italic Times-Bold Times-BoldItalic+-- > Helvetica Helvetica-Oblique Helvetica-Bold Helvetica-Bold-Oblique+-- > Courier Courier-Oblique Courier-Bold Courier-Bold-Oblique+-- > Symbol+--+-- List taken from Bill Casselman \'Mathematical Illustrations\' p279.+--+++-- | @ /... findfont @+--+ps_findfont :: String -> Doc+ps_findfont ss = command "findfont" [text $ '/':ss]++-- | @ ... scalefont @+--+ps_scalefont :: Int -> Doc+ps_scalefont sz = command "scalefont" [int sz]++-- | @ setfont @+--+ps_setfont :: Doc+ps_setfont = command "setfont" []++-- | @ (...) show @+--+ps_show :: String -> Doc+ps_show ss = command "show" [parens $ text ss]++-- | @ (/...) show @+--+ps_glyphshow :: String -> Doc+ps_glyphshow ss = command "glyphshow" [text $ '/':ss]+
src/Wumpus/Core/PtSize.hs view
@@ -9,7 +9,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- Numeric type representing Point size (1/72 inch) which is -- PostScript and Wumpus-Core\'s internal unit size.@@ -34,7 +34,9 @@ ) where --- | Wumpus-Core +-- | Wrapped Double representing /Point size/ for font metrics +-- etc.+-- newtype PtSize = PtSize { ptSize :: Double } deriving (Eq,Ord,Num,Floating,Fractional)
− src/Wumpus/Core/SVG.hs
@@ -1,436 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# OPTIONS -Wall #-}------------------------------------------------------------------------------------- |--- Module : Wumpus.Core.SVG--- Copyright : (c) Stephen Tetley 2009-2010--- License : BSD3------ Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more------ SVG generation.------ SVG is represented using XML.Light. XML.Light is a simple,--- generic XML representation (almost) everything is an element --- with attributes.------ SVG output is monadic to handle clipping paths and --- configurable text encoding via a Reader monad. ------ SVG does not achieve clipping by changing the graphics state --- (being /declarative/ SVG doesn\'t have a graphics state as --- such). Instead a clipping path has an id, subsequent elements --- that are bound by the clipping path are tagged with a --- @clip-path@ attribute that references the clipping path id: ------ > clip-path=\"url(#clip1)\"--- --- --- The operations to build XML elements (e.g. element_path) don\'t --- take more parameters than necessary, and are expected to be --- augmented with attributes using 'add_attr' and 'add_attrs' from --- the XML.Light library.--- -----------------------------------------------------------------------------------module Wumpus.Core.SVG - (- -- * SVG Monad - SvgM- , runSVG- , newClipLabel- , currentClipLabel --- -- * Build SVG- , SvgPath-- , unqualAttr- , xmlVersion- , svgDocType- , gElement- , svgElement- - , element_circle- , element_ellipse- , attr_x- , attr_y- , attr_r- , attr_rx- , attr_ry- , attr_cx- , attr_cy- , element_path- , element_clippath- , element_text- , element_tspan- , content_text- , attr_font_family- , attr_font_size- , attr_font_weight- , attr_font_style- , attr_id- , attr_fill- , attr_fill_none- , attr_stroke- , attr_stroke_none- , attr_stroke_width- , attr_stroke_miterlimit- , attr_stroke_linecap- , attr_stroke_linejoin-- , attr_stroke_dasharray- , attr_stroke_dasharray_none- , attr_stroke_dashoffset-- , attr_color- , attr_clippath- , attr_transform- , val_matrix- , val_colour- , val_rgb- , val_url- , val_translate- , path_m- , path_l- , path_c--- ) where--import Wumpus.Core.Colour-import Wumpus.Core.GraphicsState-import Wumpus.Core.TextEncoder-import Wumpus.Core.Utils---import MonadLib hiding ( version )-import Text.XML.Light---data SvgState = SvgSt { clipCount :: Int }---- | The SVG monad - which wraps a state monad to generate --- fresh names.-type SvgM a = SvgT Id a--newtype SvgT m a = SvgT { unSvgT :: StateT SvgState (ReaderT TextEncoder m) a }--runSvgT :: Monad m => TextEncoder -> SvgT m a -> m (a,SvgState)-runSvgT i m = runReaderT i $ runStateT st0 $ unSvgT m where- st0 = SvgSt { clipCount = 0 } --instance Monad m => Functor (SvgT m) where- fmap f (SvgT mf) = SvgT $ fmap f mf --instance Monad m => Monad (SvgT m) where- return a = SvgT $ return a- ma >>= f = SvgT $ unSvgT ma >>= unSvgT . f--instance Monad m => StateM (SvgT m) SvgState where- get = SvgT $ get- set = SvgT . set--instance Monad m => ReaderM (SvgT m) TextEncoder where- ask = SvgT $ ask--instance MonadT SvgT where- lift = SvgT . lift . lift---svgId :: TextEncoder -> SvgT Id a -> (a,SvgState)-svgId = runId `oo` runSvgT ---- | Run the SVG monad.-runSVG :: TextEncoder -> SvgM a -> a-runSVG = fst `oo` svgId----- | Get the current clip label.-currentClipLabel :: SvgM String-currentClipLabel = get >>= return . clipname . clipCount---- | Generate a new clip label.-newClipLabel :: SvgM String-newClipLabel = do - i <- (get >>= return . clipCount)- sets_ (\s -> s { clipCount=i+1 })- return $ clipname i---clipname :: Int -> String-clipname = ("clip" ++) . show-------------------------------------------------------------------------------------- Helpers for XML.Light and /data in strings/.---- | Helper for XML.Light-unqualAttr :: String -> String -> Attr-unqualAttr name val = Attr (unqual name) val-------------------------------------------------------------------------------------- SVG helpers--type SvgPath = [String]----- | @ \<?xml version=\"1.0\" encoding=\"...\"?\> @----xmlVersion :: String -> CData-xmlVersion s = CData CDataRaw - ("<?xml version=\"1.0\" encoding=\"" ++ s ++ "\"?>")- (Just 1)---- |--- > <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" --- > "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > ----svgDocType :: CData-svgDocType = CData CDataRaw (line1 ++ "\n" ++ line2) (Just 1)- where- line1 = "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\""- line2 = " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"---- | --- > <g> ... </g>------ Wumpus uses the g element (group) to achieve nesting. -gElement :: [Attr] -> [Element] -> Element-gElement xs ys = unode "g" (xs,ys)---- |--- > <svg xmlns="http://www.w3.org/2000/svg" version="1.1">--- > ...--- > </svg>----svgElement :: [Element] -> Element-svgElement xs = unode "svg" ([xmlns,version],xs)- where- xmlns = unqualAttr "xmlns" "http://www.w3.org/2000/svg"- version = unqualAttr "version" "1.1" ----------------------------------------------------------------------------------------- |--- > <circle/>----element_circle :: Element-element_circle = unode "circle" ()---- |--- > <ellipse/>----element_ellipse :: Element-element_ellipse = unode "ellipse" ()------ | @ x=\"...\" @-attr_x :: PSUnit u => u -> Attr-attr_x = unqualAttr "x" . dtrunc---- | @ y=\"...\" @-attr_y :: PSUnit u => u -> Attr-attr_y = unqualAttr "y" . dtrunc---- | @ r=\"...\" @-attr_r :: PSUnit u => u -> Attr-attr_r = unqualAttr "r" . dtrunc----- | @ rx=\"...\" @-attr_rx :: PSUnit u => u -> Attr-attr_rx = unqualAttr "rx" . dtrunc---- | @ ry=\"...\" @-attr_ry :: PSUnit u => u -> Attr-attr_ry = unqualAttr "ry" . dtrunc---- | @ cx=\"...\" @-attr_cx :: PSUnit u => u -> Attr-attr_cx = unqualAttr "cx" . dtrunc---- | @ cy=\"...\" @-attr_cy :: PSUnit u => u -> Attr-attr_cy = unqualAttr "cy" . dtrunc------- |--- > <path d="..." />------ Note the argument to this function is an attribute rather--- than content. We have no use for empty paths.-element_path :: SvgPath -> Element-element_path = unode "path" . attr_d---- |--- > <clipPath>--- > ...--- > </clipPath>----element_clippath :: SvgPath -> Element-element_clippath = unode "clipPath" . element_path---- |--- > <text>...</text>----element_text :: Node t => t -> Element-element_text = unode "text" ---- |--- > <text>...</text>----element_tspan :: String -> Element-element_tspan = unode "tspan" . content_text----- | Render the string as 'CDataText' - see XML.Light.-content_text :: String -> Content-content_text str = Text $ CData CDataRaw str Nothing----- | @ font-family=\"...\" @-attr_font_family :: String -> Attr-attr_font_family = unqualAttr "font-family" ---- | @ font-size=\"...\" @-attr_font_size :: Int -> Attr-attr_font_size = unqualAttr "font-size" . show---- | @ font-weight=\"...\" @-attr_font_weight :: String -> Attr-attr_font_weight = unqualAttr "font-weight"---- | @ font-style=\"...\" @-attr_font_style :: String -> Attr-attr_font_style = unqualAttr "font-style"----- | @ id=\"...\" @-attr_id :: String -> Attr-attr_id = unqualAttr "id" ---- | @ d="..." @-attr_d :: SvgPath -> Attr-attr_d = unqualAttr "d" . hsep---- | @ fill=\"rgb(..., ..., ...)\" @-attr_fill :: PSColour c => c -> Attr-attr_fill = unqualAttr "fill" . val_colour---- | @ fill=\"none\" @-attr_fill_none :: Attr-attr_fill_none = unqualAttr "fill" "none"---- | @ stroke=\"rgb(..., ..., ...)\" @-attr_stroke :: PSColour c => c -> Attr-attr_stroke = unqualAttr "stroke" . val_colour---- | @ stroke=\"none\" @-attr_stroke_none :: Attr-attr_stroke_none = unqualAttr "stroke" "none"---- | @ stroke-width=\"...\" @-attr_stroke_width :: PSUnit u => u -> Attr-attr_stroke_width = unqualAttr "stroke-width" . dtrunc----- | @ stroke-miterlimit=\"...\" @-attr_stroke_miterlimit :: PSUnit u => u -> Attr-attr_stroke_miterlimit = unqualAttr "stroke-miterlimit" . dtrunc---- | @ stroke-linejoin=\"...\" @-attr_stroke_linejoin :: LineJoin -> Attr-attr_stroke_linejoin JoinMiter = unqualAttr "stroke-linejoin" "miter"-attr_stroke_linejoin JoinRound = unqualAttr "stroke-linejoin" "round"-attr_stroke_linejoin JoinBevel = unqualAttr "stroke-linejoin" "bevel"----attr_stroke_linecap :: LineCap -> Attr-attr_stroke_linecap CapButt = unqualAttr "stroke-linecap" "butt"-attr_stroke_linecap CapRound = unqualAttr "stroke-linecap" "round"-attr_stroke_linecap CapSquare = unqualAttr "stroke-linecap" "square"----- | @ stroke-dasharray=\"...\" @-attr_stroke_dasharray :: [Int] -> Attr-attr_stroke_dasharray = unqualAttr "stroke-dasharray" . commasep . map show---- | @ stroke-dasharray=\"none\" @-attr_stroke_dasharray_none :: Attr-attr_stroke_dasharray_none = unqualAttr "stroke-dasharray" "none"---- | @ stroke-dashoffset=\"...\" @-attr_stroke_dashoffset :: Int -> Attr-attr_stroke_dashoffset = unqualAttr "stroke-dashoffset" . show---- | @ color=\"rgb(..., ..., ...)\" @------ Gray or HSB values will be converted to and rendered as RGB.-attr_color :: PSColour c => c -> Attr-attr_color = unqualAttr "color" . val_colour---- | @ clip-path=\"url(#...)\" @-attr_clippath :: String -> Attr-attr_clippath = unqualAttr "clip-path" . val_url---- | @ transform="..." @-attr_transform :: String -> Attr-attr_transform = unqualAttr "transform"---- | @ matrix(..., ..., ..., ..., ..., ...) @-val_matrix :: PSUnit u => u -> u -> u -> u -> u -> u -> String-val_matrix a b c d e f = "matrix" ++ tupled (map dtrunc [a,b,c,d,e,f])------ | @ rgb(..., ..., ...) @--- --- HSB and gray scale are translated to RGB values.-val_colour :: PSColour c => c -> String-val_colour = val_rgb . psColour----- | @ rgb(..., ..., ...) @-val_rgb :: RGB3 Double -> String-val_rgb (RGB3 r g b) = "rgb" ++ show (ramp255 r,ramp255 g,ramp255 b)----- | @ url(#...) @-val_url :: String -> String-val_url s = "url" ++ parens ('#':s)---- | @ translate(..., ...) @-val_translate :: PSUnit u => u -> u -> String-val_translate x y = "translate" ++ tupled (map dtrunc [x,y])- --- | @ M ... ... @------ c.f. PostScript's @moveto@.-path_m :: PSUnit u => u -> u -> String-path_m x y = hsep $ "M" : map dtrunc [x,y]---- | @ L ... ... @------ c.f. PostScript's @lineto@.-path_l :: PSUnit u => u -> u -> String-path_l x y = hsep $ "L" : map dtrunc [x,y]---- | @ S ... ... ... ... ... ... @--- --- c.f. PostScript's @curveto@.-path_c :: PSUnit u => u -> u -> u -> u -> u -> u -> String-path_c x1 y1 x2 y2 x3 y3 = hsep $ "C" : map dtrunc [x1,y1,x2,y2,x3,y3]--
+ src/Wumpus/Core/SVGDoc.hs view
@@ -0,0 +1,382 @@+{-# OPTIONS -Wall #-}++--------------------------------------------------------------------------------+-- |+-- Module : Wumpus.Core.SVGDoc+-- Copyright : (c) Stephen Tetley 2009-2010+-- License : BSD3+--+-- Maintainer : Stephen Tetley <stephen.tetley@gmail.com>+-- Stability : highly unstable+-- Portability : GHC+--+-- SVG Doc combinators.+--+--------------------------------------------------------------------------------++module Wumpus.Core.SVGDoc+ (+ escapeSpecial++ , xml_version+ , doctype+ , elem_svg+ , elem_g+ , elem_g_no_attrs++ , elem_a_xlink + , elem_clipPath+ , elem_path+ , elem_text+ , elem_tspan+ , elem_ellipse+ , elem_circle++ , attr_id+ , attr_x+ , attr_y+ , attr_r+ , attr_rx+ , attr_ry+ , attr_cx+ , attr_cy++ , path_m+ , path_l+ , path_c++ , val_rgb++ , attr_font_family+ , attr_font_size+ , attr_font_weight+ , attr_font_style++ , attr_fill+ , attr_fill_none+ , attr_stroke+ , attr_stroke_none+ , attr_stroke_width+ , attr_stroke_miterlimit+ , attr_stroke_linecap+ , attr_stroke_linejoin++ , attr_stroke_dasharray+ , attr_stroke_dasharray_none+ , attr_stroke_dashoffset++ , attr_clip_path+ , attr_transform+ , val_matrix+ , val_translate++ ) where++import Wumpus.Core.Colour+import Wumpus.Core.FormatCombinators+import Wumpus.Core.Geometry+import Wumpus.Core.GraphicsState+import Wumpus.Core.PictureInternal+import Wumpus.Core.TextEncoder+import Wumpus.Core.Utils+++escapeSpecial :: CharCode -> String+escapeSpecial i = "&#" ++ show i ++ ";"+++-- Note - it is easier put particular attrs at the end (esp. d +-- for paths) if attrs are treated as a Doc. +++svgElem :: String -> Doc -> Doc+svgElem name attrs = angles (text name <+> attrs <+> char '/')++svgElemB :: String -> Doc -> Doc -> Doc+svgElemB name attrs body = vcat [ open, indent 2 body, close ]+ where+ open = angles (text name <+> attrs)+ close = angles (char '/' <> text name)++svgElemB_no_attrs :: String -> Doc -> Doc+svgElemB_no_attrs name body = vcat [ open, indent 2 body, close ]+ where+ open = angles (text name)+ close = angles (char '/' <> text name)++-- 1 line version of svgElemB+--+svgElemB1 :: String -> Doc -> Doc -> Doc+svgElemB1 name attrs body = open <> body <> close+ where+ open = angles (text name <+> attrs)+ close = angles (char '/' <> text name)+++svgAttr :: String -> Doc -> Doc+svgAttr name val = text name <> char '=' <> dquotes val+ +dquoteText :: String -> Doc+dquoteText = dquotes . text ++--------------------------------------------------------------------------------+xml_version :: Doc+xml_version = text "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"++doctype :: Doc +doctype = angles ( text "!DOCTYPE svg PUBLIC" + <+> dquoteText "-//W3C//DTD SVG 1.1//EN"+ <+> dquoteText svg_url )+ where+ svg_url = "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"+++elem_svg :: Doc -> Doc +elem_svg body = svgElemB "svg" (svgns <+> svgvn <+> xlink) body+ where + svgns = svgAttr "xmlns" (text "http://www.w3.org/2000/svg")+ svgvn = svgAttr "version" (text "1.1")+ xlink = svgAttr "xmlns:xlink" (text "http://www.w3.org/1999/xlink")++-- | @ \<g ...\> ... \</g\> @ +--+elem_g :: Doc -> Doc -> Doc+elem_g attrs body = svgElemB "g" attrs body++-- | @ \<g\> ... \<g/\> @ +--+elem_g_no_attrs :: Doc -> Doc+elem_g_no_attrs body = svgElemB_no_attrs "g" body++-- | @ \<clipPath ...\> ... \</clipPath\> @ +--+elem_clipPath :: Doc -> Doc -> Doc+elem_clipPath attrs body = svgElemB "clipPath" attrs body+++elem_a_xlink :: String -> Doc -> Doc+elem_a_xlink href body = svgElemB "a" attrs body+ where+ attrs = svgAttr "xlink:href" (text href)++++-- | @ \<path ... d=... /\> @+--+elem_path :: Doc -> Doc -> Doc+elem_path attrs path = svgElem "path" (attrs <+> svgAttr "d" path)+++-- | @ \<text ... >...\</text\> @+--+elem_text :: Doc -> Doc -> Doc+elem_text attrs body = svgElemB "text" attrs body+++-- | @ \<tspan ... >...\</tspan\> @+--+elem_tspan :: Doc -> Doc -> Doc+elem_tspan attrs body1 = svgElemB1 "tspan" attrs body1++-- @ \<circle ... /\>+--+elem_circle :: Doc -> Doc+elem_circle attrs = svgElem "circle" attrs++-- @ \<ellipse ... /\>+--+elem_ellipse :: Doc -> Doc+elem_ellipse attrs = svgElem "ellipse" attrs++-- | @ id=\"...\" @+--+attr_id :: String -> Doc+attr_id = svgAttr "id" . text+++-- | @ x=\"...\" @+--+attr_x :: PSUnit u => u -> Doc+attr_x = svgAttr "x" . dtruncFmt++-- | @ y=\"...\" @+--+attr_y :: PSUnit u => u -> Doc+attr_y = svgAttr "y" . dtruncFmt++-- | @ r=\"...\" @+--+attr_r :: PSUnit u => u -> Doc+attr_r = svgAttr "r" . dtruncFmt+++-- | @ rx=\"...\" @+--+attr_rx :: PSUnit u => u -> Doc+attr_rx = svgAttr "rx" . dtruncFmt++-- | @ ry=\"...\" @+--+attr_ry :: PSUnit u => u -> Doc+attr_ry = svgAttr "ry" . dtruncFmt++-- | @ cx=\"...\" @+--+attr_cx :: PSUnit u => u -> Doc+attr_cx = svgAttr "cx" . dtruncFmt++-- | @ cy=\"...\" @+--+attr_cy :: PSUnit u => u -> Doc+attr_cy = svgAttr "cy" . dtruncFmt+++++--------------------------------------------------------------------------------+-- Path Segments, encoded as string values.+++-- | @ M ... ... @+--+-- c.f. PostScript's @moveto@.+--+path_m :: PSUnit u => Point2 u -> Doc+path_m (P2 x y) = char 'M' <+> dtruncFmt x <+> dtruncFmt y++-- | @ L ... ... @+--+-- c.f. PostScript's @lineto@.+--+path_l :: PSUnit u => Point2 u -> Doc+path_l (P2 x y) = char 'L' <+> dtruncFmt x <+> dtruncFmt y++-- | @ C ... ... ... ... ... ... @+-- +-- c.f. PostScript's @curveto@.+--+path_c :: PSUnit u => Point2 u -> Point2 u -> Point2 u -> Doc+path_c (P2 x1 y1) (P2 x2 y2) (P2 x3 y3) =+ char 'C' <+> dtruncFmt x1 <+> dtruncFmt y1+ <+> dtruncFmt x2 <+> dtruncFmt y2+ <+> dtruncFmt x3 <+> dtruncFmt y3+++val_rgb :: RGBi -> Doc+val_rgb (RGBi r g b) = + text "rgb" <> tupled [integral r, integral g, integral b]+++-- | @ font-family=\"...\" @+--+attr_font_family :: String -> Doc+attr_font_family = svgAttr "font-family" . text++-- | @ font-size=\"...\" @+--+attr_font_size :: Int -> Doc+attr_font_size = svgAttr "font-size" . int++-- | @ font-weight=\"...\" @+--+attr_font_weight :: String -> Doc+attr_font_weight = svgAttr "font-weight" . text++-- | @ font-style=\"...\" @+--+attr_font_style :: String -> Doc+attr_font_style = svgAttr "font-style" . text+++-- | @ fill=\"rgb(..., ..., ...)\" @+--+attr_fill :: RGBi -> Doc+attr_fill = svgAttr "fill" . val_rgb ++-- | @ fill=\"none\" @+--+attr_fill_none :: Doc+attr_fill_none = svgAttr "fill" (text "none")++-- | @ stroke=\"rgb(..., ..., ...)\" @+--+attr_stroke :: RGBi -> Doc+attr_stroke = svgAttr "stroke" . val_rgb++-- | @ stroke=\"none\" @+--+attr_stroke_none :: Doc+attr_stroke_none = svgAttr "stroke" (text "none")++-- | @ stroke-width=\"...\" @+--+attr_stroke_width :: PSUnit u => u -> Doc+attr_stroke_width = svgAttr "stroke-width" . dtruncFmt+++-- | @ stroke-miterlimit=\"...\" @+--+attr_stroke_miterlimit :: PSUnit u => u -> Doc+attr_stroke_miterlimit = svgAttr "stroke-miterlimit" . dtruncFmt++-- | @ stroke-linejoin=\"...\" @+--+attr_stroke_linejoin :: LineJoin -> Doc+attr_stroke_linejoin = svgAttr "stroke-linejoin" . step + where+ step JoinMiter = text "miter"+ step JoinRound = text "round"+ step JoinBevel = text "bevel"+++-- | @ stroke-linecap=\"...\" @+--+attr_stroke_linecap :: LineCap -> Doc+attr_stroke_linecap = svgAttr "stroke-linecap" . step+ where+ step CapButt = text "butt"+ step CapRound = text "round"+ step CapSquare = text "square"+++-- | @ stroke-dasharray=\"...\" @+--+attr_stroke_dasharray :: [(Int,Int)] -> Doc+attr_stroke_dasharray = svgAttr "stroke-dasharray" . step + where+ step [] = empty+ step [(a,b)] = int a <> comma <> int b + step ((a,b):xs) = int a <> comma <> int b <> step xs++-- | @ stroke-dasharray=\"none\" @+--+attr_stroke_dasharray_none :: Doc+attr_stroke_dasharray_none = svgAttr "stroke-dasharray" (text "none")++-- | @ stroke-dashoffset=\"...\" @+--+attr_stroke_dashoffset :: Int -> Doc+attr_stroke_dashoffset = svgAttr "stroke-dashoffset" . int++-- | @ clip_path="url(#...)" @+--+attr_clip_path :: String -> Doc+attr_clip_path ss = svgAttr "transform" (text "url" <> parens (text $ '#':ss)) +++-- | @ transform="..." @+--+attr_transform :: Doc -> Doc+attr_transform = svgAttr "transform"++-- | @ matrix(..., ..., ..., ..., ..., ...) @+--+val_matrix :: PSUnit u => Matrix3'3 u -> Doc+val_matrix mtrx = text "matrix" <> tupled (map dtruncFmt [a,b,c,d,e,f])+ where+ (a,b,c,d,e,f) = deconsMatrix mtrx++-- | @ translate(..., ..., ..., ..., ..., ...) @+--+val_translate :: PSUnit u => Vec2 u -> Doc+val_translate (V2 x y) = text "translate" <> tupled [dtruncFmt x, dtruncFmt y]
src/Wumpus/Core/TextEncoder.hs view
@@ -8,7 +8,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- Extended character code handling. -- @@ -48,8 +48,8 @@ -type GlyphName = String-type CharCode = Int +type GlyphName = String+type CharCode = Int type PostScriptLookup = CharCode -> Maybe GlyphName type SVGLookup = GlyphName -> Maybe CharCode
− src/Wumpus/Core/TextEncodingInternal.hs
@@ -1,101 +0,0 @@-{-# OPTIONS -Wall #-}------------------------------------------------------------------------------------- |--- Module : Wumpus.Core.TextEncodingInternal--- Copyright : (c) Stephen Tetley 2009-2010--- License : BSD3------ Maintainer : stephen.tetley@gmail.com--- Stability : unstable--- Portability : GHC with TypeFamilies and more------ Extended character handling...--- -----------------------------------------------------------------------------------module Wumpus.Core.TextEncodingInternal- ( -- EncodedText(..) - , TextChunk(..)-- , textLength- , lookupByCharCode - , lookupByGlyphName-- , lexLabel-- ) where--import Wumpus.Core.TextEncoder--import Text.PrettyPrint.Leijen hiding ( SText )--import Data.Char--newtype EncodedText = EncodedText { getEncodedText :: [TextChunk] }- deriving (Eq,Show)---data TextChunk = SText String- | EscInt Int- | EscStr GlyphName- deriving (Eq,Show)-------------------------------------------------------------------------------------instance Pretty EncodedText where- pretty = hcat . map pretty . getEncodedText--instance Pretty TextChunk where- pretty (SText s) = string s- pretty (EscInt i) = text "&#" <> int i <> semi- pretty (EscStr s) = text "&#" <> text s <> semi------------------------------------------------------------------------------------textLength :: EncodedText -> Int-textLength = foldr add 0 . getEncodedText where - add (SText s) n = n + length s- add _ n = n + 1---lookupByCharCode :: CharCode -> TextEncoder -> Maybe GlyphName-lookupByCharCode i enc = (ps_lookup enc) i--lookupByGlyphName :: GlyphName -> TextEncoder -> Maybe CharCode-lookupByGlyphName i enc = (svg_lookup enc) i----- | Output to PostScript as @ /egrave glyphshow @---- Output to SVG as an escaped decimal, e.g. @ è @------ Note, HTML entity names do not seem to be supported in SVG,--- @ è @ does not work in FireFox or Chrome.---lexLabel :: String -> EncodedText-lexLabel = EncodedText . lexer--lexer :: String -> [TextChunk]-lexer [] = []--lexer ('&':'#':xs) = esc xs- where- esc (c:cs) | isDigit c = let (s,cs') = span isDigit cs - in intval (c:s) cs'- | otherwise = let (s,cs') = span isAlpha cs - in EscStr (c:s) : optsemi cs'- esc [] = []-- optsemi (';':cs) = lexer cs -- let ill-formed go through- optsemi cs = lexer cs-- intval [] rest = optsemi rest- intval cs rest = EscInt (read cs) : optsemi rest--lexer (x:xs) = let (s,xs') = span (/= '&') xs - in SText (x:s) : lexer xs'
+ src/Wumpus/Core/TextInternal.hs view
@@ -0,0 +1,100 @@+{-# OPTIONS -Wall #-}++--------------------------------------------------------------------------------+-- |+-- Module : Wumpus.Core.TextInternal+-- Copyright : (c) Stephen Tetley 2009-2010+-- License : BSD3+--+-- Maintainer : stephen.tetley@gmail.com+-- Stability : unstable+-- Portability : GHC+--+-- Extended character handling.+-- +--------------------------------------------------------------------------------++module Wumpus.Core.TextInternal+ ( ++ EncodedText(..) + , TextChunk(..)++ , textLength+ , lookupByCharCode + , lookupByGlyphName++ , lexLabel++ ) where++import Wumpus.Core.FormatCombinators+import Wumpus.Core.TextEncoder++import Data.Char++newtype EncodedText = EncodedText { getEncodedText :: [TextChunk] }+ deriving (Eq,Show)+++data TextChunk = SText String+ | EscInt Int+ | EscStr GlyphName+ deriving (Eq,Show)+++--------------------------------------------------------------------------------++instance Format EncodedText where+ format = hcat . map format . getEncodedText++instance Format TextChunk where+ format (SText s) = text s+ format (EscInt i) = text "&#" <> int i <> semicolon+ format (EscStr s) = text "&#" <> text s <> semicolon++--------------------------------------------------------------------------------++textLength :: EncodedText -> Int+textLength = foldr add 0 . getEncodedText where + add (SText s) n = n + length s+ add _ n = n + 1+++lookupByCharCode :: CharCode -> TextEncoder -> Maybe GlyphName+lookupByCharCode i enc = (ps_lookup enc) i++lookupByGlyphName :: GlyphName -> TextEncoder -> Maybe CharCode+lookupByGlyphName i enc = (svg_lookup enc) i+++-- | Output to PostScript as @ /egrave glyphshow @++-- Output to SVG as an escaped decimal, e.g. @ è @+--+-- Note, HTML entity names do not seem to be supported in SVG,+-- @ è @ does not work in FireFox or Chrome.+++lexLabel :: String -> EncodedText+lexLabel = EncodedText . lexer++lexer :: String -> [TextChunk]+lexer [] = []++lexer ('&':'#':xs) = esc xs+ where+ esc (c:cs) | isDigit c = let (s,cs') = span isDigit cs + in intval (c:s) cs'+ | otherwise = let (s,cs') = span isAlpha cs + in EscStr (c:s) : optsemi cs'+ esc [] = []++ optsemi (';':cs) = lexer cs -- let ill-formed go through+ optsemi cs = lexer cs++ intval [] rest = optsemi rest+ intval cs rest = EscInt (read cs) : optsemi rest++lexer (x:xs) = let (s,xs') = span (/= '&') xs + in SText (x:s) : lexer xs'
src/Wumpus/Core/TextLatin1.hs view
@@ -8,7 +8,7 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- A TextEncoder record instance for Latin1 characters. --
src/Wumpus/Core/Utils.hs view
@@ -11,7 +11,7 @@ -- Stability : highly unstable -- Portability : GHC ----- Utility functions and a OneList (non-empty list) data type.+-- Utility functions and a Hughes list. -- -------------------------------------------------------------------------------- @@ -19,82 +19,66 @@ module Wumpus.Core.Utils ( + -- | Opt - maybe strict in Some+ Opt(..)+ , some - -- * Three values - max3- , min3- , med3+ -- | Conditional application+ , applyIf + , rescale -- * Truncate / print a double , PSUnit(..)+ , dtruncFmt+ , truncateDouble , roundup+ - , rescale- , clamp- , ramp- , ramp255-- -- * PostScript timetmap- , mkTimeStamp-- -- * Pretty printers for strings - , parens- , hsep- , commasep- , tupled-- -- * Extras - , sequenceA- , (<:>) + -- * PostScript time stamp+ , psTimeStamp -- * Hughes list , H , emptyH- , toListH+ , wrapH+ , consH , snocH --- -- * specs etc. from Data.Aviary- , appro- , oo- , ooo- , oooo- , rap-+ , appendH+ , toListH ) where +import qualified Wumpus.Core.FormatCombinators as Fmt -import Control.Applicative-import Data.List ( intersperse )+ import Data.Ratio import Data.Time ------------------------------------------------------------------------------------+data Opt a = None | Some !a + deriving (Eq,Show) --- | max of 3-max3 :: Ord a => a -> a -> a -> a-max3 a b c = max (max a b) c+some :: a -> Opt a -> a+some dflt None = dflt+some _ (Some a) = a --- | min of 3-min3 :: Ord a => a -> a -> a -> a-min3 a b c = min (min a b) c+applyIf :: Bool -> (a -> a) -> a -> a+applyIf cond fn a = if cond then fn a else a --- | median of 3-med3 :: Ord a => a -> a -> a -> a-med3 a b c = if c <= x then x else if c > y then y else c- where - (x,y) = order a b- order p q | p <= q = (p,q)- | otherwise = (q,p)+-- rescale a (originally in the range amin to amax) within the +-- the range bmin to bmax.+--+rescale :: Fractional a => (a,a) -> (a,a) -> a -> a+rescale (amin,amax) (bmin,bmax) a = + bmin + apos * (brange / arange) + where+ arange = amax - amin+ brange = bmax - bmin+ apos = a - amin --------------------------------------------------------------------------------@@ -119,7 +103,10 @@ instance PSUnit (Ratio Int) where toDouble = realToFrac +dtruncFmt :: PSUnit a => a -> Fmt.Doc+dtruncFmt = Fmt.text . dtrunc + -- | Truncate the printed decimal representation of a Double. -- The is prefered to 'showFFloat' from Numeric as it produces -- shorter representations where appropriate.@@ -147,80 +134,37 @@ ceilingi = ceiling --- rescale a (originally in the range amin to amax) within the --- the range bmin to bmax.----rescale :: Fractional a => (a,a) -> (a,a) -> a -> a-rescale (amin,amax) (bmin,bmax) a = - bmin + apos * (brange / arange) - where- arange = amax - amin- brange = bmax - bmin- apos = a - amin -clamp :: Ord a => a -> a -> a -> a -clamp amin amax x = max amin (min amax x) -ramp :: Double -> Double-ramp = clamp 0 1---- | Scale a Double between 0.0 and 1.0 to be an Int between 0 --- and 255.-ramp255 :: Double -> Int-ramp255 = clamp 0 255 . ceiling . (*255)+-------------------------------------------------------------------------------- ---------------------------------------------------------------------------------+-- | To be used with getZonedTime --- | Generate a time stamp for the output files. Note PostScript--- does no interpretation of the time stamp, it is solely for --- information and so the representation is arbitrary.+psTimeStamp :: ZonedTime -> ShowS+psTimeStamp zt = localTimeS . showChar ' ' . localDayS+ where+ local_tim = zonedTimeToLocalTime zt+ localTimeS = timeOfDay $ localTimeOfDay $ local_tim+ localDayS = showString $ showGregorian $ localDay local_tim -mkTimeStamp :: IO String-mkTimeStamp = getZonedTime >>= return . format . zonedTimeToLocalTime+timeOfDay :: TimeOfDay -> ShowS+timeOfDay t = + fn todHour . showChar ':' . fn todMin . showChar ':' . fn (floori . todSec) where- format t = mkTime t ++ " " ++ mkDate t- mkTime = concat . intersperse ":" . sequenceA tfuns . localTimeOfDay- mkDate = showGregorian . localDay- tfuns = [ pad2 . todHour, pad2 . todMin, pad2 . floori . todSec ]- pad2 i | i < 10 = '0' : show i- | otherwise = show i + fn f = pad2 (f t) -floori :: RealFrac a => a -> Int-floori = floor +pad2 :: Int -> ShowS+pad2 i | i < 10 = ('0':) . shows i+ | otherwise = shows i --------------------------------------------------------------------------------- --- | Enclose string in parens.-parens :: String -> String -parens s = "(" ++ s ++ ")"---- | Separate with a space.-hsep :: [String] -> String-hsep = concat . intersperse " "--commasep :: [String] -> String-commasep = concat . intersperse ","---- | @ (..., ...)@-tupled :: [String] -> String-tupled = parens . concat . intersperse ", " +floori :: RealFrac a => a -> Int+floori = floor --- | Applicative version of (monadic) 'sequence'.--- Because we use MonadLib we don't want to bring in --- Control.Monad.Instances ()-sequenceA :: Applicative f => [f a] -> f [a]-sequenceA = foldr (<:>) (pure []) ----- | Applicative 'cons'.-infixr 6 <:>-(<:>) :: Applicative f => f a -> f [a] -> f [a]-(<:>) a b = (:) <$> a <*> b- -------------------------------------------------------------------------------- -- Hughes list @@ -229,64 +173,20 @@ emptyH :: H a emptyH = id -toListH :: H a -> [a]-toListH = ($ []) -snocH :: H a -> a -> H a-snocH hl a = hl . (a:)-------------------------------------------------------------------------------------- | A variant of the @D2@ or dovekie combinator - the argument--- order has been changed to be more satisfying for Haskellers:------ > (appro comb f g) x y------ > (f x) `comb` (g y)--- --- @on@ from Data.Function is similar but less general, where --- the two intermediate results are formed by applying the same --- function to the supplied arguments:------ > on = (appro comb f f)----appro :: (c -> d -> e) -> (a -> c) -> (b -> d) -> a -> b -> e-appro comb f g x y = comb (f x) (g y) -------------------------------------------------------------------------------------- Specs - blackbird, bunting, ...---- Alleviate your composing-sectioning mania with specs!------ E.g.:--- (abs .) . (*) ==> abs `oo` (*)------ The family name /specs/ (glasses, specs, lunettes) is a --- visual pun when infix directives @`oo`@ are included. The --- @o@\'s of individual combinators are a fraternal nod to --- Clean and ML who use @o@ as function composition. Naturally--- we don\'t defined @o@ here and waste a good variable on a --- redundant combinator.+wrapH :: a -> H a+wrapH a = consH a id --- | Compose an arity 1 function with an arity 2 function.--- B1 - blackbird-oo :: (c -> d) -> (a -> b -> c) -> a -> b -> d-oo f g = (f .) . g+consH :: a -> H a -> H a+consH a f = (a:) . f --- | Compose an arity 1 function with an arity 3 function.--- B2 - bunting-ooo :: (d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e-ooo f g = ((f .) .) . g+snocH :: H a -> a -> H a+snocH hl a = hl . (a:) --- | Compose an arity 1 function with an arity 4 function.-oooo :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f-oooo f g = (((f .) .) .) . g +appendH :: H a -> H a -> H a+appendH f g = f . g +toListH :: H a -> [a]+toListH = ($ []) --- ($) reversed - aka T - aka (#)----infixl 1 `rap`-rap :: a -> (a -> b) -> b-rap a f = f a
src/Wumpus/Core/VersionNumber.hs view
@@ -8,9 +8,9 @@ -- -- Maintainer : stephen.tetley@gmail.com -- Stability : unstable--- Portability : GHC with TypeFamilies and more+-- Portability : GHC ----- Version number+-- Version number. -- -------------------------------------------------------------------------------- @@ -20,9 +20,9 @@ ) where --- | Version number+-- | Version number. ----- > (0,23,0)+-- > (0,30,0) -- wumpus_core_version :: (Int,Int,Int)-wumpus_core_version = (0,23,0)+wumpus_core_version = (0,30,0)
src/Wumpus/Core/WumpusTypes.hs view
@@ -8,7 +8,7 @@ -- -- Maintainer : Stephen Tetley <stephen.tetley@gmail.com> -- Stability : unstable --- Portability : GHC with TypeFamilies and more+-- Portability : GHC -- -- This module re-exports types and functions from -- "Wumpus.Core.PictureInternal" but makes them opaque. @@ -28,27 +28,28 @@ -- * Picture types Picture , DPicture+ , GSUpdate , Primitive , DPrimitive- , Path- , DPath- , PathSegment- , DPathSegment- , Label- , DLabel+ , PrimPath+ , DPrimPath+ , PrimPathSegment+ , DPrimPathSegment+ , PrimLabel+ , DPrimLabel -- * Drawing styles , PathProps , LabelProps , EllipseProps- , DrawPath- , DrawEllipse ++ -- * Transformations on Primitives- , translatePrimitive , rotatePrimitive , scalePrimitive , uniformScalePrimitive+ , translatePrimitive -- * Printable unit for PostScript@@ -58,5 +59,5 @@ import Wumpus.Core.PictureInternal-import Wumpus.Core.Utils ()+import Wumpus.Core.Utils ( PSUnit(..) )
wumpus-core.cabal view
@@ -1,5 +1,5 @@ name: wumpus-core-version: 0.23.0+version: 0.30.0 license: BSD3 license-file: LICENSE copyright: Stephen Tetley <stephen.tetley@gmail.com>@@ -29,9 +29,9 @@ . For actually drawing pictures, diagrams, etc. Wumpus is very low level. There is a supplemantary package @wumpus-basic@ - available that helps create certain types of diagram, but even - this lacks the higher-level stuff (polygons, arrows etc.) that - would make creating general drawings easy.+ available that helps create certain types of diagram, but it is+ experimental - functionality is added an dropped between + releases. . Some of the design decisions made for wumpus-core are not sophisticated (e.g. how attributes like colour are handled, @@ -43,14 +43,7 @@ appears okay for Latin 1 but may be inadequate for other character sets, so I may have to revise it significantly. .- WARNING... .- The module @Core.BoundingBox@ is likely to be reworked - significantly in the future. It has too many functions that do - not offer distinct functionality. Some functions were removed in - revision 0.17.0 and some more are likely to follow. - .- . \[1\] Because the output is simple, straight-line PostScript code, it is possible to use GraphicsMagick or a similar tool to convert Wumpus'\s EPS files to many other formats @@ -58,6 +51,47 @@ . Changelog: .+ 0.23.0 to 0.30.0:+ .+ * Substantial changes - the output machinery has been heavily + modified, the intention was to reduce the use of matrix + transformations in SVG output. Though this goal has not been + achieved, some code-size improvements in the SVG output have + been made. + .+ * The code generation monads have been changed - Wumpus no + longer uses a writer monad for code generation, but it will+ keep the Wumpus name.+ .+ * Added support for hyperlinks in the SVG output.+ .+ * Improved internal pretty printing and added the function + @printPicture@ for debugging.+ .+ * The @Core.Colour@ module has been simplified to support just + one (new) type - RGBi, and the conversion type class + @PSColour@ has been eliminated.+ .+ * @Core.Picture@ - the type of the @frame@ function has been + changed to take a list of Primitives rather than a single + Primitive. The function @frameMulti@ which previously + provided this functionality has been removed. SVG hyperlinks+ are now supported for Primitives. + . + * The default font size has been changed to 14 pt. + .+ * The Path datatype has been renamed PrimPath, likewise Ellipse + is now PrimEllispe and Label becomes PrimLabel.+ .+ * @Core.Geometry@ now longer defines an affine frame datatype.+ .+ * @Core.BoundingBox@ module changed significantly. The corner + functions (@boundaryBottomLeft@, etc.) have been removed, + along with the plane functions (@leftPlane@, etc.). Some + functions have been given more verbose names - @corners@ is + now @boundaryCorners@, @within@ is now @withinBoundary@ and + @union@ is now @boundaryUnion@. + . 0.22.0 TO 0.23.0: . * @Basic.GraphicsState@ - extracted the font face fields from @@ -94,10 +128,11 @@ demo/AffineTestBase.hs, demo/FontMetrics.hs demo/LabelPic.hs,- demo/MultiPic.hs- demo/Rotated.hs- demo/Scaled.hs- demo/Translated.hs+ demo/MultiPic.hs,+ demo/Rotated.hs,+ demo/Scaled.hs,+ demo/Translated.hs,+ demo/ZOrderPic.hs, doc/Guide.pdf, doc-src/Guide.lhs, doc-src/Makefile,@@ -107,10 +142,11 @@ library hs-source-dirs: src- build-depends: base < 5, containers,- time >= 1.1.3 && < 1.2,- wl-pprint, vector-space, - monadLib, xml, algebra+ build-depends: base < 5, + containers >= 0.3 && <= 0.4, + time >= 1.1.3 && < 1.2,+ vector-space >= 0.6 && < 1.0,+ algebra >= 0.0.0.1 exposed-modules: Wumpus.Core,@@ -130,11 +166,13 @@ Wumpus.Core.WumpusTypes other-modules:+ Wumpus.Core.FormatCombinators,+ Wumpus.Core.PageTranslation, Wumpus.Core.PictureInternal,- Wumpus.Core.PostScript,- Wumpus.Core.SVG,+ Wumpus.Core.PostScriptDoc,+ Wumpus.Core.SVGDoc, Wumpus.Core.OneList,- Wumpus.Core.TextEncodingInternal,+ Wumpus.Core.TextInternal, Wumpus.Core.Utils extensions: