gb-vector: Pure Haskell SVG generation

[ bsd3, graphics, library ] [ Propose Tags ] [ Report a vulnerability ]

A pure Haskell library for generating SVG. Composable Element tree — style and transforms are constructors that wrap children. No XML library, no IO, just pure functions building Text. Dependencies: base + text only.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.18 && <5), text (>=2.0 && <2.2) [details]
Tested with ghc ==9.6.7
License BSD-3-Clause
Author Devon Tomlin
Maintainer devon.tomlin@novavero.ai
Uploaded by aoinoikaz at 2026-03-02T19:03:43Z
Category Graphics
Home page https://github.com/Gondola-Bros-Entertainment/gb-vector
Bug tracker https://github.com/Gondola-Bros-Entertainment/gb-vector/issues
Source repo head: git clone https://github.com/Gondola-Bros-Entertainment/gb-vector.git
Distributions
Downloads 1 total (1 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-03-02 [all 1 reports]

Readme for gb-vector-0.1.0.0

[back to package description]

gb-vector

Pure Haskell SVG Generation

Composable Element tree — no XML library, no IO, just pure functions building Text.

Overview · Architecture · Usage · API · Example

CI Hackage Haskell License


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

Transform & Style

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