Vg is a declarative 2D vector graphics library ported from OCaml to MoonBit. Images are values that denote functions mapping points of the cartesian plane to colors and combinators are provided to define and compose them.
This is a MoonBit port of the original Vg library by Daniel Bünzli.
- ✅ Core Types: Point, Color, Transform, Path, Image
- ✅ Color Utilities: Predefined colors, blending, RGBA, HSV support
- ✅ Point Operations: Distance, dot product, normalization, rotation
- ✅ Transformations: Translation, scaling, rotation, skewing, composition
- ✅ Basic Shapes: Circle, rectangle, line, ellipse, polygon
- ✅ Image Combinators: Shapes, gradients, composition, cutting, opacity
- ✅ Path Construction: Move, line, curve, close operations with OO-style API
- ✅ Advanced Paths: Circle, ellipse, rectangle path generation with method chaining
- ✅ Fluent API: Object-oriented method calls with
Path::empty().move_to().line_to()
- ✅ Multiple Renderers: SVG, PDF, and Canvas rendering backends
- ✅ Gradients: Linear, radial, axial, and conic gradients with color interpolation
- ✅ Modern Syntax: String interpolation and contemporary MoonBit idioms
- ✅ 100% Feature Parity: Complete port of original OCaml Vg library functionality
- ✅ Comprehensive Tests: Extensive test suite for all components
- ✅ WebAssembly Target: Compiles to WebAssembly via MoonBit
# Clone the repository
git clone https://github.com/moonbit-community/vg.git
cd vg
# Build and check the library
moon check
moon build
# Run tests to verify installation
moon test
# Run the main demo
moon run src/main
# Generate documentation
moon info
test "basic shapes" {
// Create basic shapes
let red_circle = @vg.circle(@vg.red(), 50.0)
let blue_ellipse = @vg.ellipse(@vg.blue(), 60.0, 40.0)
let triangle = @vg.polygon(@vg.green(), [@vg.point(0.0, -30.0), @vg.point(-30.0, 30.0), @vg.point(30.0, 30.0)])
// Apply transformations and effects
let semi_transparent = @vg.with_opacity(red_circle, 0.7)
let translated_ellipse = @vg.translate_img(50.0, 0.0, blue_ellipse)
// Compose images
let composed = @vg.compose_imgs(semi_transparent, translated_ellipse)
// Create paths with object-oriented API
let custom_path = @vg.Path::empty()
.move_to(@vg.point(10.0, 10.0))
.line_to(@vg.point(90.0, 10.0))
.curve_to(@vg.point(110.0, 10.0), @vg.point(110.0, 30.0), @vg.point(90.0, 30.0))
.close_path()
// Create SVG output with advanced shapes
let svg_doc = @vg.new_svg(200.0, 200.0)
.render_circle(@vg.point(100.0, 100.0), 50.0, @vg.red())
.render_ellipse(@vg.point(150.0, 100.0), 30.0, 20.0, @vg.blue())
.render_path(custom_path, @vg.green())
let svg_string = svg_doc.to_string()
// Use the variables to avoid unused warnings
ignore(triangle)
ignore(composed)
ignore(svg_string)
}
The library is organized into several modules:
types.mbt
: Core type definitions (Point, Color, Transform, etc.)color.mbt
: Color utilities and predefined colorspoint.mbt
: Point operations and vector mathtransform.mbt
: 2D transformation matricesimage.mbt
: Image combinators and shape primitivespath.mbt
: Path construction and manipulation with OO-style APIsvg.mbt
: SVG rendering backend with fluent document APIcanvas.mbt
: HTML5 Canvas rendering backendpdf.mbt
: PDF document generation backend
test "basic shapes examples" {
// Create a red circle
let circle_img = @vg.circle(@vg.red(), 25.0)
// Create a blue rectangle
let rect_img = @vg.rectangle(@vg.blue(), 50.0, 30.0)
// Create an ellipse
let ellipse_img = @vg.ellipse(@vg.green(), 40.0, 20.0)
// Create a polygon (triangle)
let triangle = @vg.polygon(@vg.yellow(), [
@vg.point(0.0, -20.0),
@vg.point(-20.0, 20.0),
@vg.point(20.0, 20.0)
])
// Use the variables to avoid unused warnings
ignore(circle_img)
ignore(rect_img)
ignore(ellipse_img)
ignore(triangle)
}
test "transformations examples" {
let circle_img = @vg.circle(@vg.red(), 25.0)
let rect_img = @vg.rectangle(@vg.blue(), 50.0, 30.0)
// Translate an image
let moved = @vg.translate_img(10.0, 20.0, circle_img)
// Scale an image
let scaled = @vg.scale_image(2.0, 1.5, rect_img)
// Rotate an image
let rotated = @vg.rotate_image(3.14159 / 4.0, circle_img) // 45 degrees
// Use the variables to avoid unused warnings
ignore(moved)
ignore(scaled)
ignore(rotated)
}
test "colors and effects examples" {
// HSV color creation
let bright_orange = @vg.hsv(30.0, 1.0, 1.0) // Hue, Saturation, Value
// Color interpolation
let purple_to_cyan = @vg.lerp_color(@vg.purple(), @vg.cyan(), 0.5)
// Apply opacity
let semi_transparent = @vg.with_opacity(@vg.circle(@vg.red(), 30.0), 0.6)
// Linear gradient
let gradient = @vg.linear_gradient(
@vg.red(), @vg.blue(),
@vg.point(-50.0, 0.0), @vg.point(50.0, 0.0)
)
// Radial gradient
let radial = @vg.radial_gradient(
@vg.white(), @vg.black(),
@vg.point(0.0, 0.0), 50.0
)
// Use the variables to avoid unused warnings
ignore(bright_orange)
ignore(purple_to_cyan)
ignore(semi_transparent)
ignore(gradient)
ignore(radial)
}
test "paths examples" {
// Create a custom path with method chaining
let path = @vg.Path::empty()
.move_to(@vg.point(10.0, 10.0))
.line_to(@vg.point(90.0, 10.0))
.curve_to(@vg.point(110.0, 10.0), @vg.point(110.0, 30.0), @vg.point(90.0, 30.0))
.close_path()
// Create predefined shapes
let rectangle = @vg.Path::rect(0.0, 0.0, 50.0, 30.0)
let circle = @vg.Path::circle(@vg.point(25.0, 25.0), 20.0)
let ellipse = @vg.Path::ellipse(@vg.point(0.0, 0.0), 30.0, 15.0)
// Transform paths
let transform = @vg.make_translate(10.0, 20.0)
let moved_path = path.transform(transform)
// Get path bounds
match path.bounds() {
Some(bounds) => println("Path bounds: " + bounds.to_string())
None => println("Empty path")
}
// Render path to SVG
let svg = @vg.new_svg(100.0, 100.0)
.render_path(path, @vg.green())
// Use the variables to avoid unused warnings
ignore(rectangle)
ignore(circle)
ignore(ellipse)
ignore(moved_path)
ignore(svg)
}
test "canvas rendering examples" {
let custom_path = @vg.Path::empty()
.move_to(@vg.point(10.0, 10.0))
.line_to(@vg.point(50.0, 10.0))
.close_path()
// Create an HTML5 Canvas document with fluent method chaining
let canvas_doc = @vg.new_canvas(400.0, 300.0)
.render_circle(@vg.point(100.0, 100.0), 50.0, @vg.red())
.render_rectangle(150.0, 50.0, 80.0, 60.0, @vg.blue())
.render_path(custom_path, @vg.green())
.render_text("Hello Canvas!", @vg.point(200.0, 200.0), 16.0, @vg.black())
// Generate JavaScript code
let js_code = canvas_doc.to_js()
// Generate complete HTML page
let html_page = canvas_doc.to_html("My Canvas Demo")
// Use the variables to avoid unused warnings
ignore(js_code)
ignore(html_page)
}
test "pdf generation examples" {
let star_path = @vg.Path::empty()
.move_to(@vg.point(0.0, -20.0))
.line_to(@vg.point(5.0, -5.0))
.line_to(@vg.point(20.0, -5.0))
.close_path()
// Create a PDF document with fluent method chaining
let pdf_doc = @vg.PdfDocument::new(210.0, 297.0) // A4 size
.render_circle(@vg.point(105.0, 100.0), 30.0, @vg.red())
.render_rectangle(50.0, 150.0, 110.0, 50.0, @vg.blue())
.render_path(star_path, @vg.gold())
.render_text("PDF Graphics Demo", @vg.point(50.0, 250.0), 14.0, @vg.black())
// Generate PDF string
let pdf_content = pdf_doc.to_string()
// Use the variable to avoid unused warning
ignore(pdf_content)
}
The Path API has been updated to use MoonBit's object-oriented style. Here's how to migrate:
test "functional style example" {
// This is the old functional style (no longer supported)
// let path = empty_path()
// |> move_to(point(0.0, 0.0))
// |> line_to(point(10.0, 0.0))
// |> close_path()
//
// let rect = rect_path(0.0, 0.0, 10.0, 5.0)
// let bounds = path_bounds(path)
// let transformed = transform_path(transform, path)
// Placeholder to make test valid
let _ = 1
}
test "object oriented style example" {
let path = @vg.Path::empty()
.move_to(@vg.point(0.0, 0.0))
.line_to(@vg.point(10.0, 0.0))
.close_path()
let rect = @vg.Path::rect(0.0, 0.0, 10.0, 5.0)
let bounds = path.bounds()
let transform = @vg.make_translate(5.0, 5.0)
let transformed = path.transform(transform)
// Use the variables to avoid unused warnings
ignore(rect)
ignore(bounds)
ignore(transformed)
}
✅ Complete and Production-Ready: The library has achieved 100% feature parity with the original OCaml Vg library, featuring:
- Full API Modernization: Complete migration to object-oriented fluent APIs
- Multiple Rendering Backends: SVG, PDF, and Canvas support with consistent APIs
- Modern MoonBit Syntax: String interpolation and contemporary language idioms
- Comprehensive Testing: Extensive test coverage with snapshot validation
- Zero Compiler Warnings: Clean, maintainable codebase following best practices
The library successfully compiles and runs across all target platforms, demonstrating robust implementation of declarative 2D vector graphics for the MoonBit ecosystem.
ISC License (same as original Vg library)
Original Vg library by Daniel Bünzli: https://github.com/dbuenzli/vg MoonBit port with extensive tests and examples.