BSD-3-Clause licensed by Devon Tomlin
Maintained by [email protected]
This version can be pinned in stack with:gb-vector-0.1.0.5@sha256:8802ff5f39056057d2dba0af2296dfcf2174a116a43fd7286122cfde88f97fdd,1794
Depends on 2 packages(full list with versions):

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; clip polygon must be convex)
  • 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
  • 523 tests, 92% HPC expression coverage, 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
  | ...  -- 30 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.8.

cabal build                              # Build library
cabal test                               # Run tests (523 pure tests)
cabal build --ghc-options="-Werror"      # Warnings as errors
cabal haddock                            # Generate docs (100% coverage)

Changes

Changelog

0.1.0.5

Bug Fixes

  • Fix gradient rendering under transforms. Add gradientUnits="userSpaceOnUse" to both linear and radial gradient SVG output. Without this, browsers default to objectBoundingBox coordinates which misinterpret absolute gradient positions when the element tree contains scale/translate transforms.

0.1.0.4

Improvements

  • Fix all Haddock ambiguity warnings (disambiguate type-vs-constructor references)
  • Fix missing link for unexported intersectEpsilon in Boolean module docs
  • Add cabal haddock step to CI pipeline to catch doc warnings before publish

0.1.0.3

Bug Fixes

  • Fix SVG parser failing to parse width/height on <svg> tags containing URL attributes (e.g. xmlns="http://..."). The / in quoted URLs was prematurely terminating the tag scan, causing round-trip render → parseSvg to lose document dimensions.

Improvements

  • 523 tests (was 294), 92% HPC expression coverage across all 17 modules

0.1.0.2

Improvements

  • Document all positional constructor arguments with -- | comments for richer API docs on Hackage
  • Upgrade tested GHC from 9.6.7 to 9.8.4
  • Fix GHC 9.8 -Wcompat warnings (head/last replaced with pattern matching)

Affected Types

  • V2, ViewBox, Segment, Color — all constructor arguments documented
  • Element — all 30 constructors’ positional arguments documented
  • Fill, Gradient, FilterKind — all constructor arguments documented
  • ParseErrorMalformedTag and MalformedPath arguments documented

0.1.0.1

Bug Fixes

  • Fix arc segments silently replaced with straight lines in Boolean ops and PathOps
  • Fix arc length measurement using straight-line distance instead of true arc length
  • Fix subpath potential overflow when t0 is very close to 1
  • Fix test suite leaving temporary SVG files in project root

Improvements

  • cubicLength now uses adaptive flattening instead of uniform sampling for better accuracy
  • grad2D expanded from 4 to 8 gradient directions for better noise isotropy
  • 100% Haddock coverage across all 17 modules (was 64%)
  • Document Sutherland-Hodgman convexity requirement on intersection and difference
  • Document union behavior with non-overlapping polygons
  • Document intersectEpsilon as absolute tolerance
  • 9 new tests (294 total): arc flattening, arc measurement, arc boolean ops, subpath edge case, saturate safety, noise determinism, arcToCubics validation

Internal

  • Add directory to test suite dependencies for temp file cleanup

0.1.0.0

Initial release.

Core Types

  • V2 2D vector, Segment (line/cubic/quad/arc), Path (closed/open)
  • Color type with RGBA in [0, 1]rgb, rgba, rgb8, hex constructors
  • 43 named colors, lerp, withAlpha, toHex
  • hsl, hsla — HSL color construction
  • Oklab perceptual color space: toOklab, fromOklab, lerpOklab
  • Color adjustments: lighten, darken, saturate, desaturate, invert

Element Tree

  • Element recursive sum type — style and transforms as wrapping constructors
  • Compose via function application or left-to-right with (&)
  • Semigroup/Monoid instance via EGroup

Shapes

  • circle, rect, roundedRect, ellipse, polygon, star
  • line, square, regularPolygon, arc, ring

Path DSL

  • buildPath — run a PathBuilder monad to produce a Path
  • PathBuilder actions: startAt, lineTo, cubicTo, quadTo, arcTo, closePath
  • polylinePath, polygonPath convenience constructors

Path Operations

  • reversePath, measurePath — reversal and arc length
  • splitPathAt, subpath — splitting and extraction
  • offsetPath — parallel curve approximation
  • simplifyPath — Ramer-Douglas-Peucker simplification

Boolean Operations

  • union, intersection, difference, xorPaths — polygon clipping
  • pathToPolygon, polygonToPath — conversion utilities
  • polygonArea, pointInPolygon — polygon analysis

Transforms

  • translate, rotate, rotateAround, scale, scaleXY, skewX, skewY
  • Matrix type with identity, composeMatrix, applyMatrix
  • Matrix constructors: translateM, rotateM, scaleM, scaleXYM, skewXM, skewYM

Gradients

  • linearGradient, radialGradient — gradient constructors
  • stop, stopWithOpacity, evenStops, oklabStops — gradient stop builders

Style

  • fill, fillColor, fillGradient, fillNone, fillRule
  • stroke, strokeEx, dashedStroke, defaultStrokeConfig
  • opacity, clip, mask, blur, dropShadow
  • withId, use, raw, title, desc

Text

  • text, textAt, textWithConfig — text element constructors
  • defaultTextConfig, fontSize, fontFamily, bold, italic, anchor — config builders

Noise

  • perlin2D, simplex2D — deterministic 2D noise
  • fbm — fractional Brownian motion
  • noisePath, noiseClosedPath — procedural path generation
  • wobblePath, jitterPoints — noise-driven distortion
  • voronoiCells, voronoiEdges — Voronoi diagram generation

Patterns

  • dotGrid, lineGrid, crosshatch, checker — tileable pattern generators
  • patternDef, PatternConfig, defaultPatternConfig — SVG pattern element construction

SVG Parsing

  • parseSvg, parseElement — parse SVG text back to Element trees
  • Supports basic shapes, paths, groups, text, and presentation attributes
  • Enables round-trip workflows: render, parse, manipulate, re-export

Composition

  • group, empty, document, documentWithViewBox, background
  • optimizeElement — collapse redundant transforms and empty groups

SVG Output

  • render :: Document -> Text — pure SVG serialization
  • renderCompact :: Document -> Text — compact SVG output
  • renderElement :: Element -> Text — render a fragment without <svg> wrapper
  • writeSvg :: FilePath -> Document -> IO () — file output