Overview
gb-vector is a pure Haskell library for generating SVG. Define shapes, style them, compose them, and render — all with pure functions.
Companion to gb-sprite (procedural raster) and gb-synth (procedural audio).
Features:
Element recursive sum type — style and transforms are constructors wrapping children
- Compose via function application (
translate 50 50 $ fill gold $ circle 30) or (&)
- Shapes: circle, ellipse, rect, rounded rect, polygon, star, arc, ring
- Path DSL: monadic builder with lineTo, cubicTo, quadTo, arcTo, closePath
- Path operations: reverse, measure, split, offset, simplify (Ramer-Douglas-Peucker)
- Boolean operations: union, intersection, difference, XOR (Sutherland-Hodgman)
- Transforms: translate, rotate, rotateAround, scale, skew + affine matrix type
- Style: fill, stroke, opacity, clip, mask, blur, drop shadow
- Color: 43 named colors, hex, HSL, Oklab perceptual space, lighten/darken/saturate/invert
- Gradients: linear, radial, with stop helpers and Oklab interpolation
- Noise: Perlin, simplex, FBM, noise-driven paths, Voronoi diagrams
- Patterns: dot grid, line grid, crosshatch, checker
- SVG parsing: round-trip parse/render for basic shapes, paths, groups, transforms
- Bezier math: De Casteljau evaluation, subdivision, flattening, arc-to-cubic
- Text elements with font configuration
- Tree optimizer: collapse redundant transforms and empty groups
- Semigroup/Monoid composition on Element
- 285 tests, 100% Haddock coverage
Dependencies: base + text only. Both GHC boot libraries. Zero external deps.
Architecture
src/GBVector/
├── Types.hs V2, Segment, ArcParams, Path, enums (LineCap, LineJoin, FillRule)
├── Color.hs RGBA, rgb/rgb8/hex/hsl, 43 named colors, Oklab, lighten/darken
├── Element.hs Element tree, Fill, Gradient, StrokeConfig, FilterKind, Document
├── Path.hs PathBuilder DSL (startAt/lineTo/cubicTo/closePath/buildPath)
├── PathOps.hs reverse, measure, split, offset, simplify paths
├── Boolean.hs union, intersection, difference, XOR polygon clipping
├── Bezier.hs De Casteljau, subdivision, bbox, arc-to-cubic, flatten, length
├── Shape.hs circle, rect, roundedRect, ellipse, polygon, star, arc, ring
├── Gradient.hs linearGradient, radialGradient, stop, evenStops, oklabStops
├── Noise.hs perlin2D, simplex2D, fbm, noisePath, wobblePath, Voronoi
├── Pattern.hs dotGrid, lineGrid, crosshatch, checker, patternDef
├── Transform.hs translate, rotate, scale, skew + Matrix type with composition
├── Style.hs fill, stroke, opacity, clip, mask, blur, dropShadow, withId, use
├── Compose.hs group, empty, document, background, optimizeElement
├── Text.hs text, textAt, textWithConfig, font config builders
├── SVG.hs render :: Document -> Text, writeSvg :: FilePath -> Document -> IO ()
└── SVG/
└── Parse.hs parseSvg, parseElement — SVG text back to Element trees
Pipeline
Shapes/Paths → Style/Transform → Compose → Document → render → Text/SVG file
Usage
As a dependency
Add to your .cabal file:
build-depends: gb-vector >= 0.1
Generating SVG
import GBVector.Color (gold)
import GBVector.Compose (document)
import GBVector.SVG (writeSvg)
import GBVector.Shape (star)
import GBVector.Style (fill)
import GBVector.Transform (translate)
main :: IO ()
main = writeSvg "star.svg" $
document 200 200 $
translate 100 100 $
fill gold $
star 5 80 35
API
Color
data Color = Color !Double !Double !Double !Double -- RGBA [0,1]
rgb :: Double -> Double -> Double -> Color
rgba :: Double -> Double -> Double -> Double -> Color
rgb8 :: Int -> Int -> Int -> Color -- 0-255 channels
hex :: String -> Color -- "#ff0000", "f00", etc.
hsl :: Double -> Double -> Double -> Color -- hue (0-360), saturation, lightness
hsla :: Double -> Double -> Double -> Double -> Color
lerp :: Double -> Color -> Color -> Color -- linear interpolation
lerpOklab :: Double -> Color -> Color -> Color -- perceptual interpolation
withAlpha :: Double -> Color -> Color
toHex :: Color -> String -- "#rrggbb" or "#rrggbbaa"
lighten :: Double -> Color -> Color -- increase lightness in Oklab
darken :: Double -> Color -> Color
saturate :: Double -> Color -> Color
desaturate :: Double -> Color -> Color
invert :: Color -> Color
-- 43 named colors: black, white, red, green, blue, yellow, cyan, magenta,
-- gold, crimson, coral, navy, purple, violet, teal, olive, ...
Element
data Element
= ECircle !Double | ERect !Double !Double | EPath !Path | EGroup ![Element]
| EFill !Fill !Element | EStroke !Color !Double !Element
| ETranslate !Double !Double !Element | ERotate !Double !Element
| EScale !Double !Double !Element | EOpacity !Double !Element
| EClip !Element !Element | EFilter !FilterKind !Element
| ... -- 27 constructors total
instance Semigroup Element -- EGroup composition
instance Monoid Element -- mempty = EEmpty
Shape
circle :: Double -> Element -- radius
ellipse :: Double -> Double -> Element -- rx, ry
rect :: Double -> Double -> Element -- width, height
square :: Double -> Element
roundedRect :: Double -> Double -> Double -> Double -> Element -- w, h, rx, ry
line :: V2 -> V2 -> Element
polygon :: [V2] -> Element
regularPolygon :: Int -> Double -> Element -- sides, radius
star :: Int -> Double -> Double -> Element -- points, outer, inner
arc :: Double -> Double -> Double -> Element -- radius, start, end (radians)
ring :: Double -> Double -> Element -- outer, inner radius
Path DSL
buildPath :: PathBuilder () -> Path
startAt :: V2 -> PathBuilder ()
lineTo :: V2 -> PathBuilder ()
cubicTo :: V2 -> V2 -> V2 -> PathBuilder () -- control1, control2, end
quadTo :: V2 -> V2 -> PathBuilder () -- control, end
arcTo :: ArcParams -> V2 -> PathBuilder ()
closePath :: PathBuilder ()
polylinePath :: [V2] -> Path -- open path through points
polygonPath :: [V2] -> Path -- closed path through points
Path Operations
reversePath :: Path -> Path
measurePath :: Path -> Double -- approximate arc length
splitPathAt :: Double -> Path -> (Path, Path) -- split at t in [0,1]
subpath :: Double -> Double -> Path -> Path
offsetPath :: Double -> Path -> Path -- parallel curve approximation
simplifyPath :: Double -> Path -> Path -- Ramer-Douglas-Peucker
Boolean Operations
union :: Path -> Path -> Path
intersection :: Path -> Path -> Path
difference :: Path -> Path -> Path -- A minus B
xorPaths :: Path -> Path -> Path
pathToPolygon :: Path -> [V2]
polygonToPath :: [V2] -> Path
polygonArea :: [V2] -> Double
pointInPolygon :: V2 -> [V2] -> Bool
translate :: Double -> Double -> Element -> Element
rotate :: Double -> Element -> Element -- degrees
rotateAround :: Double -> V2 -> Element -> Element
scale :: Double -> Element -> Element -- uniform
scaleXY :: Double -> Double -> Element -> Element
skewX :: Double -> Element -> Element -- degrees
skewY :: Double -> Element -> Element
fill :: Color -> Element -> Element
stroke :: Color -> Double -> Element -> Element -- color, width
opacity :: Double -> Element -> Element
fillNone :: Element -> Element
clip :: Element -> Element -> Element -- clip shape, content
mask :: Element -> Element -> Element
blur :: Double -> Element -> Element -- stdDeviation
dropShadow :: Double -> Double -> Double -> Color -> Element -> Element
Noise
perlin2D :: Int -> Double -> Double -> Double -- seed, x, y
simplex2D :: Int -> Double -> Double -> Double
fbm :: (Double -> Double -> Double) -> Int -> Double -> Double -> Double -> Double -> Double
noisePath :: (Double -> Double -> Double) -> Int -> Double -> Double -> Double -> Path
noiseClosedPath :: (Double -> Double -> Double) -> Int -> Double -> Double -> Double -> Double -> Path
wobblePath :: (Double -> Double -> Double) -> Double -> Path -> Path
jitterPoints :: (Double -> Double -> Double) -> Double -> [V2] -> [V2]
voronoiCells :: Int -> Int -> Int -> Double -> Double -> [V2]
voronoiEdges :: Int -> Int -> Int -> Double -> Double -> [(V2, V2)]
SVG Output
render :: Document -> Text -- pure serialization
renderElement :: Element -> Text -- fragment (no <svg> wrapper)
writeSvg :: FilePath -> Document -> IO ()
SVG Parsing
parseSvg :: Text -> Either ParseError Document
parseElement :: Text -> Either ParseError Element
Example
import Data.Function ((&))
import GBVector.Color (black, gold, hex, red, white)
import GBVector.Compose (background, document, group)
import GBVector.Element (Element (EPath))
import GBVector.Gradient (evenStops, linearGradient)
import GBVector.Path (buildPath, closePath, cubicTo, lineTo, startAt)
import GBVector.SVG (writeSvg)
import GBVector.Shape (circle, rect, star)
import GBVector.Style (fill, fillGradient, fillNone, stroke, withId)
import GBVector.Transform (rotate, scale, translate)
import GBVector.Types (V2 (..))
main :: IO ()
main = writeSvg "example.svg" $
document 400 400 $
background 400 400 (hex "#1a1a2e") $
group
[ -- Gold star with black outline
star 5 80 35
& fill gold
& stroke black 2
& translate 200 180
, -- Gradient circle
circle 40
& fillGradient (linearGradient (V2 0 0) (V2 80 80) (evenStops [red, gold]))
& translate 200 320
, -- Custom path
EPath (buildPath $ do
startAt (V2 50 50)
lineTo (V2 150 50)
cubicTo (V2 200 100) (V2 200 200) (V2 150 250)
lineTo (V2 50 250)
closePath)
& fillNone
& stroke white 1.5
& translate 100 50
]
Build & Test
Requires GHCup with GHC >= 9.6.
cabal build # Build library
cabal test # Run tests (285 pure tests)
cabal build --ghc-options="-Werror" # Warnings as errors
cabal haddock # Generate docs (100% coverage)
BSD-3-Clause License · Gondola Bros Entertainment