wumpus-core-0.12.0: Pure Haskell PostScript and SVG generation.

PortabilityGHC only




Internal representation of Pictures


Data types

data Picture u Source

Picture is a leaf attributed tree - where atttibutes are colour, line-width etc. It is parametric on the unit type of points (typically Double).

Wumpus's Picture, being a leaf attributed tree, is not ideally matched to PostScript's picture representation, which might be considered a node attributed tree if you recast graphics state updates as syntactic commands encountered during top-down evaluation.

Currently this mismatch means that the PostScript code generated by Wumpus has significant overuse of PostScript's gsave and grestore.

At some point a tree-rewriting step might be added to coalesce some of the repeated graphics state updates.

Apropos the constructors, Picture is a simple non-empty leaf-labelled rose tree via

 Single (aka leaf) | Picture (OneList tree)

Where OneList is a variant of the standard list type that disallows empty lists.

The additional constructors are convenience:

PickBlank 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.


PicBlank (Locale u) 
Single (Locale u) (Primitive u) 
Picture (Locale u) (OneList (Picture u)) 
Clip (Locale u) (Path u) (Picture u) 


Eq u => Eq (Picture u) 
Show u => Show (Picture u) 
(Num u, Pretty u) => Pretty (Picture u) 
Num u => Blank (Picture u) 
(Num u, Ord u, Horizontal (Picture u), Vertical (Picture u)) => Move (Picture u) 
(Num u, Ord u) => Composite (Picture u) 
(Num u, Ord u) => Vertical (Picture u) 
(Num u, Ord u) => Horizontal (Picture u) 
(Num u, Ord u) => Translate (Picture u) 
(Num u, Ord u) => Scale (Picture u) 
(Floating u, Real u) => RotateAbout (Picture u) 
(Floating u, Real u) => Rotate (Picture u) 
Boundary (Picture u) 

data Primitive u Source

Wumpus's drawings are built from two fundamental primitives: paths (line segments and Bezier curves) and labels (single lines of text).

Ellipses are a included as a primitive only for optimization - drawing a reasonable circle with Bezier curves needs at 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 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 control points.

Ellipses are represented by their center, half-width and half-height. Half-width and half-height are used so the bounding box can be calculated using only multiplication, and thus initially only obliging a Num constraint on the unit. Though typically for affine transformations a Fractional constraint is also obliged.


Eq u => Eq (Primitive u) 
Show u => Show (Primitive u) 
Pretty u => Pretty (Primitive u) 
(Fractional u, Ord u) => Boundary (Primitive u) 

data Path u Source


Path (Point2 u) [PathSegment u] 


Eq u => Eq (Path u) 
Show u => Show (Path u) 
Semigroup (Path u)

Paths are sensibly a Semigroup - there is no notion of empty path.

Pretty u => Pretty (Path u) 
Pointwise (Path u) 
(Num u, Ord u) => Boundary (Path u) 

data PathSegment u Source


PCurve (Point2 u) (Point2 u) (Point2 u) 
PLine (Point2 u) 


data Label u Source




Eq u => Eq (Label u) 
Show u => Show (Label u) 
Pretty u => Pretty (Label u) 

data DrawPath Source

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).


type Locale u = (Frame2 u, BoundingBox u)Source

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 class

class Num a => PSUnit a whereSource


extractFrame :: Num u => Picture u -> Frame2 uSource

Should this really be public?