diff --git a/README.mbt.md b/README.mbt.md
index db0d708..c9bc17a 100644
--- a/README.mbt.md
+++ b/README.mbt.md
@@ -76,7 +76,7 @@ test "basic_shapes" (it : @test.Test) {
.close_path()
// Create SVG output with advanced shapes
- let svg_doc = @vg.new_svg(200.0, 200.0)
+ let svg_doc = @svg.new_svg(200.0, 200.0)
.render_circle(@vg.Point::new(100.0, 100.0), 50.0, @color.red())
.render_ellipse(@vg.Point::new(150.0, 100.0), 30.0, 20.0, @color.blue())
.render_path(custom_path, @color.green())
@@ -225,7 +225,7 @@ test "paths examples" {
}
// Render path to SVG
- let svg = @vg.new_svg(100.0, 100.0).render_path(path, @color.green())
+ let svg = @svg.new_svg(100.0, 100.0).render_path(path, @color.green())
// Use the variables to avoid unused warnings
ignore(rectangle)
diff --git a/REFACTORING_SVG_PACKAGE.md b/REFACTORING_SVG_PACKAGE.md
new file mode 100644
index 0000000..10efbcb
--- /dev/null
+++ b/REFACTORING_SVG_PACKAGE.md
@@ -0,0 +1,195 @@
+# SVG Package Refactoring
+
+**Date:** November 22, 2025
+
+## Overview
+
+This refactoring splits out the `SvgDocument` type and all SVG rendering utilities into a separate `svg` package to improve modularity and reduce coupling in the VG vector graphics library. This follows the same pattern previously used for the geometry and color package refactorings. Importantly, the `Image::render_image_to_svg` method is kept in the main `vg` package as it represents a rendering backend method for the Image type.
+
+## Changes Made
+
+### New Package Structure
+
+Created `svg/` package with:
+- `svg/types.mbt` - Core SvgDocument type definition
+- `svg/svg.mbt` - All SVG rendering methods and utilities
+- `svg/moon.pkg.json` - Package configuration with dependencies on geometry and color packages
+
+### Type Migration
+
+**Moved from `vg` package to `svg` package:**
+- `SvgDocument` struct - SVG document representation with width, height, and elements array
+
+**SVG functions moved:**
+- Document creation: `new_svg`
+- Document manipulation: `SvgDocument::add_element`, `SvgDocument::to_string`
+- Rendering methods: `SvgDocument::render_circle`, `SvgDocument::render_rectangle`, `SvgDocument::render_path`, `SvgDocument::render_line`, `SvgDocument::render_text`, `SvgDocument::render_ellipse`, `SvgDocument::render_polygon`, `SvgDocument::render_linear_gradient`
+- Helper functions: `color_to_svg`, `point_to_svg`, `path_to_svg_data`
+
+**Kept in `vg` package:**
+- `Image::render_image_to_svg` - Image rendering method (uses `@svg` namespace internally)
+
+### Dependency Management
+
+**Main package (`vg`) changes:**
+- Updated `moon.pkg.json` to import `bobzhang/vg/svg`
+- Modified `types.mbt` to re-export SvgDocument type using type alias:
+ ```moonbit
+ pub type SvgDocument = @svg.SvgDocument
+ ```
+- Updated `svg.mbt` to only contain `Image::render_image_to_svg` method
+- Image rendering method now uses `@svg.new_svg` to create SVG documents
+
+**SVG package dependencies:**
+- Imports `bobzhang/vg/geometry` for Point and Path types
+- Imports `bobzhang/vg/color` for Color type and utilities
+
+### Code Updates Required
+
+**In test files and documentation:**
+- Replaced `@vg.new_svg` with `@svg.new_svg`
+- All SvgDocument methods now accessed through the `@svg` namespace
+
+**Examples:**
+```moonbit
+// Before:
+let doc = @vg.new_svg(100.0, 100.0)
+ .render_circle(Point::new(50.0, 50.0), 25.0, @color.red())
+
+// After:
+let doc = @svg.new_svg(100.0, 100.0)
+ .render_circle(Point::new(50.0, 50.0), 25.0, @color.red())
+```
+
+**Files updated:**
+- All test files (svg_test.mbt, oo_api_test.mbt, renderer_test.mbt, hello_test.mbt, advanced_test.mbt)
+- README.mbt.md
+- svg.mbt (now only contains Image::render_image_to_svg)
+- types.mbt (added SvgDocument type re-export)
+- moon.pkg.json (added svg package import)
+
+### Backward Compatibility
+
+The SvgDocument type remains accessible through the main package:
+- The SvgDocument type is re-exported from the main package via type alias
+- All existing type references like `SvgDocument` still work
+- However, the creation and manipulation functions now require the `@svg` namespace
+- This is a breaking change in the public API (functions moved from `@vg` to `@svg` namespace)
+
+### Test Results
+
+- All 175 tests pass
+- No errors, only existing warnings about using package-qualified names in geometry package tests
+- Build verification: `moon check` and `moon test` both succeed
+
+## Migration Guide for External Users
+
+If you're using the VG library and upgrading:
+
+### Using SVG Functions
+
+SVG functions must now be accessed through the `@svg` namespace:
+
+```moonbit
+// Creating SVG documents
+let doc = @svg.new_svg(200.0, 200.0)
+
+// Rendering shapes
+let doc = @svg.new_svg(100.0, 100.0)
+ .render_circle(@vg.Point::new(50.0, 50.0), 25.0, @color.red())
+ .render_rectangle(10.0, 10.0, 80.0, 80.0, @color.blue())
+ .render_text("Hello", @vg.Point::new(50.0, 50.0), 16.0, @color.black())
+
+// Converting to string
+let svg_string = doc.to_string()
+```
+
+### Using the SvgDocument Type
+
+The SvgDocument type is still available through the main package via type alias:
+
+```moonbit
+// Type references work as before
+let doc : SvgDocument = @svg.new_svg(100.0, 100.0)
+let doc2 : @vg.SvgDocument = @svg.new_svg(100.0, 100.0)
+```
+
+### Image to SVG Rendering
+
+The `Image::render_image_to_svg` method remains in the main `vg` package:
+
+```moonbit
+let img = @vg.Image::circle(@color.red(), 25.0)
+let svg_string = img.render_image_to_svg(100.0, 100.0, 20)
+```
+
+### Migration from Previous Version
+
+If you have existing code that used `@vg.new_svg`, update to use `@svg`:
+
+```moonbit
+// Before:
+let doc = @vg.new_svg(100.0, 100.0)
+
+// After:
+let doc = @svg.new_svg(100.0, 100.0)
+```
+
+## Benefits
+
+1. **Modularity** - SVG rendering types and utilities can be used independently of the full VG library
+2. **Clarity** - Clear separation between SVG rendering backend and core image/graphics functionality
+3. **Testability** - SVG package can have its own focused test suite
+4. **Reusability** - Other packages can depend on just the svg package if needed for SVG generation
+5. **Type Safety** - No change in type safety, all benefits of strong typing preserved
+6. **Consistency** - Follows the same pattern as the geometry and color package refactorings
+7. **Proper Architecture** - Keeps Image methods in the main package while SVG document operations are in the svg package
+
+## Files Modified
+
+- `svg/` - New package directory with types and rendering utilities
+- `types.mbt` - Added SvgDocument type re-export
+- `svg.mbt` - Reduced to only contain Image::render_image_to_svg method
+- All test files - Updated from `@vg.new_svg()` to `@svg.new_svg()`
+- `README.mbt.md` - Updated all examples to use `@svg` namespace
+- `moon.pkg.json` - Added svg package import
+
+## Files Structure
+
+```
+vg/
+├── color/
+│ ├── types.mbt # Color struct definition
+│ ├── color.mbt # Color utilities and functions
+│ ├── color_test.mbt # Color-specific tests
+│ └── moon.pkg.json # Color package configuration
+├── geometry/ # Geometry package (previously refactored)
+│ ├── types.mbt
+│ ├── point.mbt
+│ ├── path.mbt
+│ ├── transform.mbt
+│ └── moon.pkg.json
+├── svg/
+│ ├── types.mbt # SvgDocument struct definition
+│ ├── svg.mbt # SVG rendering methods and utilities
+│ └── moon.pkg.json # SVG package configuration (depends on geometry and color)
+├── types.mbt # Type re-exports (Color, Point, Path, SvgDocument, etc.)
+├── svg.mbt # Image::render_image_to_svg only
+└── moon.pkg.json # Main package configuration (imports color, geometry, svg packages)
+```
+
+## Notes
+
+- The svg package depends on both geometry and color packages
+- Type aliases ensure SvgDocument type is available through `@vg.SvgDocument`
+- All SVG functions must be accessed via `@svg` namespace (no re-export wrappers)
+- `Image::render_image_to_svg` remains in the main `vg` package as specified in the requirements
+- This refactoring changes the public API - SVG functions moved from `@vg` to `@svg` namespace
+- The pattern matches geometry and color packages: types re-exported, functions in dedicated namespace
+
+## Future Work
+
+With geometry, color, and svg now separated into independent packages, the library has a clean modular architecture. Future work could include:
+- Additional rendering backends (Canvas, PDF) as separate packages
+- Image composition and manipulation utilities as a separate package
+- More advanced SVG features (filters, animations, etc.)
diff --git a/advanced_test.mbt b/advanced_test.mbt
index 967e5fe..95af31c 100644
--- a/advanced_test.mbt
+++ b/advanced_test.mbt
@@ -11,7 +11,7 @@ test "quadratic bezier curves" (it : @test.Test) {
qcurve_path,
content="Path([MoveTo({x: 10, y: 50}), QCurveTo({x: 50, y: 10}, {x: 90, y: 50}), QCurveTo({x: 50, y: 90}, {x: 10, y: 50}), Close])",
)
- let doc = @vg.new_svg(100.0, 100.0)
+ let doc = @svg.new_svg(100.0, 100.0)
.render_rectangle(0.0, 0.0, 100.0, 100.0, @color.gray(0.95))
.render_path(qcurve_path, @color.blue())
let svg_string = doc.to_string()
@@ -30,7 +30,7 @@ test "elliptical arcs" (it : @test.Test) {
arc_path,
content="Path([MoveTo({x: 20, y: 50}), EArcTo(30, 20, 0, false, true, {x: 80, y: 50}), EArcTo(30, 20, 0, false, true, {x: 20, y: 50}), Close])",
)
- let doc = @vg.new_svg(100.0, 100.0)
+ let doc = @svg.new_svg(100.0, 100.0)
.render_rectangle(0.0, 0.0, 100.0, 100.0, @color.gray(0.95))
.render_path(arc_path, @color.green())
let svg_string = doc.to_string()
@@ -53,7 +53,7 @@ test "smooth curve stitching" (it : @test.Test) {
smooth_path,
content="Path([MoveTo({x: 10, y: 50}), CurveTo({x: 30, y: 10}, {x: 50, y: 10}, {x: 70, y: 50}), CurveTo({x: 90, y: 90}, {x: 110, y: 90}, {x: 130, y: 50}), CurveTo({x: 150, y: 10}, {x: 150, y: 10}, {x: 170, y: 50})])",
)
- let doc = @vg.new_svg(180.0, 100.0)
+ let doc = @svg.new_svg(180.0, 100.0)
.render_rectangle(0.0, 0.0, 180.0, 100.0, @color.gray(0.95))
.render_path(smooth_path, @color.purple())
let svg_string = doc.to_string()
@@ -126,7 +126,7 @@ test "checkerboard pattern" (it : @test.Test) {
///|
test "advanced path showcase" (it : @test.Test) {
- let doc = @vg.new_svg(400.0, 300.0)
+ let doc = @svg.new_svg(400.0, 300.0)
.render_rectangle(0.0, 0.0, 400.0, 300.0, @color.gray(0.98))
.render_text(
"Advanced Path Features",
diff --git a/hello_test.mbt b/hello_test.mbt
index 2d02816..938e0c4 100644
--- a/hello_test.mbt
+++ b/hello_test.mbt
@@ -69,7 +69,7 @@ test "path to SVG integration" {
.close_path()
// Render to SVG
- let doc = @vg.new_svg(150.0, 100.0).render_path(path, @color.magenta())
+ let doc = @svg.new_svg(150.0, 100.0).render_path(path, @color.magenta())
let svg_string = doc.to_string()
if not(svg_string.contains("path")) {
fail("Should contain path element")
@@ -154,7 +154,7 @@ test "color operations" {
///|
test "complete vg library showcase" (it : @test.Test) {
// Create a comprehensive demo showcasing all VG features
- let doc = @vg.new_svg(600.0, 500.0)
+ let doc = @svg.new_svg(600.0, 500.0)
.render_rectangle(0.0, 0.0, 600.0, 500.0, @color.gray(0.97)) // Light background
.render_text(
"MoonBit VG Library - Complete Showcase",
diff --git a/moon.pkg.json b/moon.pkg.json
index 5cef213..41a7e5b 100644
--- a/moon.pkg.json
+++ b/moon.pkg.json
@@ -2,6 +2,7 @@
"is-main": false,
"import": [
"bobzhang/vg/geometry",
- "bobzhang/vg/color"
+ "bobzhang/vg/color",
+ "bobzhang/vg/svg"
]
}
\ No newline at end of file
diff --git a/oo_api_test.mbt b/oo_api_test.mbt
index 7183829..5c7c941 100644
--- a/oo_api_test.mbt
+++ b/oo_api_test.mbt
@@ -3,7 +3,7 @@
///|
test "svg oo-style api" (it : @test.Test) {
// Demonstrate fluent OO-style API for SVG
- let doc = @vg.new_svg(300.0, 200.0)
+ let doc = @svg.new_svg(300.0, 200.0)
.render_rectangle(0.0, 0.0, 300.0, 200.0, @color.gray(0.95))
.render_circle(@vg.Point::new(80.0, 100.0), 30.0, @color.red())
.render_rectangle(150.0, 70.0, 60.0, 60.0, @color.green())
@@ -83,7 +83,7 @@ test "oo-style path with document integration" (it : @test.Test) {
.close_path()
// Use the same path across all OO-style renderers
- let svg_doc = @vg.new_svg(250.0, 150.0)
+ let svg_doc = @svg.new_svg(250.0, 150.0)
.render_rectangle(0.0, 0.0, 250.0, 150.0, @color.white())
.render_path(custom_path, @color.purple())
.render_text(
@@ -125,7 +125,7 @@ test "oo-style api comparison" (it : @test.Test) {
// Compare functional vs OO-style APIs
// Functional style (original)
- let svg_functional = @vg.new_svg(200.0, 100.0)
+ let svg_functional = @svg.new_svg(200.0, 100.0)
.render_circle(@vg.Point::new(100.0, 50.0), 25.0, @color.red())
.render_text(
"Functional Style",
@@ -135,7 +135,7 @@ test "oo-style api comparison" (it : @test.Test) {
)
// OO style (new)
- let svg_oo = @vg.new_svg(200.0, 100.0)
+ let svg_oo = @svg.new_svg(200.0, 100.0)
.render_circle(@vg.Point::new(100.0, 50.0), 25.0, @color.red())
.render_text("OO Style", @vg.Point::new(100.0, 80.0), 12.0, @color.black())
@@ -175,7 +175,7 @@ test "complete oo-style showcase" (it : @test.Test) {
.smooth_ccurve_to(@vg.Point::new(225.0, 125.0), @vg.Point::new(275.0, 75.0))
.earc_to(25.0, 15.0, 0.0, false, true, @vg.Point::new(225.0, 100.0))
.close_path()
- let svg_showcase = @vg.new_svg(350.0, 200.0)
+ let svg_showcase = @svg.new_svg(350.0, 200.0)
.render_rectangle(0.0, 0.0, 350.0, 200.0, @color.gray(0.98))
.render_text(
"Complete OO-Style API Showcase",
diff --git a/renderer_test.mbt b/renderer_test.mbt
index 51cf472..4f0d3da 100644
--- a/renderer_test.mbt
+++ b/renderer_test.mbt
@@ -137,7 +137,7 @@ test "renderer comparison showcase" (it : @test.Test) {
// Create the same graphics with all three renderers
// SVG version
- let svg_doc = @vg.new_svg(250.0, 150.0)
+ let svg_doc = @svg.new_svg(250.0, 150.0)
.render_rectangle(0.0, 0.0, 250.0, 150.0, @color.gray(0.95))
.render_circle(@vg.Point::new(125.0, 75.0), 40.0, @color.red())
.render_text("VG Demo", @vg.Point::new(125.0, 30.0), 16.0, @color.black())
@@ -195,7 +195,7 @@ test "complete feature showcase all renderers" (it : @test.Test) {
.close_path()
// SVG with all advanced features
- let svg_doc = @vg.new_svg(350.0, 200.0)
+ let svg_doc = @svg.new_svg(350.0, 200.0)
.render_rectangle(0.0, 0.0, 350.0, 200.0, @color.white())
.render_text(
"Complete VG Feature Showcase",
diff --git a/svg.mbt b/svg.mbt
index e774777..18076e3 100644
--- a/svg.mbt
+++ b/svg.mbt
@@ -1,206 +1,4 @@
-// SVG rendering backend
-
-///|
-/// SVG document structure
-pub struct SvgDocument {
- width : Double
- height : Double
- elements : Array[String]
-} derive(Show)
-
-///|
-/// Create a new SVG document
-pub fn new_svg(width : Double, height : Double) -> SvgDocument {
- { width, height, elements: [] }
-}
-
-///|
-/// Add an element to the SVG document (OO-style)
-pub fn SvgDocument::add_element(
- self : SvgDocument,
- element : String,
-) -> SvgDocument {
- { ..self, elements: [..self.elements, element] }
-}
-
-///|
-/// Convert a color to SVG color string
-fn color_to_svg(c : Color) -> String {
- if c.a < 1.0 {
- let r = (c.r * 255.0).to_int()
- let g = (c.g * 255.0).to_int()
- let b = (c.b * 255.0).to_int()
- "rgba(\{r},\{g},\{b},\{c.a})"
- } else {
- @color.to_hex(c)
- }
-}
-
-///|
-/// Convert a point to SVG coordinate string
-fn point_to_svg(p : Point) -> String {
- "\{p.x},\{p.y}"
-}
-
-///|
-/// Convert a path to SVG path data
-fn path_to_svg_data(path : Path) -> String {
- let mut data = ""
- for segment in path.0 {
- match segment {
- MoveTo(p) => data = data + "M \{point_to_svg(p)} "
- LineTo(p) => data = data + "L \{point_to_svg(p)} "
- CurveTo(cp1, cp2, end) =>
- data = data +
- "C \{point_to_svg(cp1)} \{point_to_svg(cp2)} \{point_to_svg(end)} "
- QCurveTo(cp, end) =>
- data = data + "Q \{point_to_svg(cp)} \{point_to_svg(end)} "
- EArcTo(rx, ry, rotation, large_arc, sweep, end) => {
- let large_flag = if large_arc { "1" } else { "0" }
- let sweep_flag = if sweep { "1" } else { "0" }
- data = data +
- "A \{rx} \{ry} \{rotation} \{large_flag} \{sweep_flag} \{point_to_svg(end)} "
- }
- Close => data = data + "Z "
- }
- }
- data
-}
-
-///|
-/// Render a circle to SVG (OO-style)
-pub fn SvgDocument::render_circle(
- self : SvgDocument,
- center : Point,
- radius : Double,
- color : Color,
-) -> SvgDocument {
- let element =
- $|
- self.add_element(element)
-}
-
-// ===== OBJECT-ORIENTED API METHODS =====
-
-///|
-/// Render a rectangle to SVG (OO-style)
-pub fn SvgDocument::render_rectangle(
- self : SvgDocument,
- x : Double,
- y : Double,
- width : Double,
- height : Double,
- color : Color,
-) -> SvgDocument {
- let element =
- $|
- self.add_element(element)
-}
-
-///|
-/// Render a path to SVG (OO-style)
-pub fn SvgDocument::render_path(
- self : SvgDocument,
- path : Path,
- color : Color,
-) -> SvgDocument {
- let data = path_to_svg_data(path)
- let element =
- $|
- self.add_element(element)
-}
-
-///|
-/// Render a line to SVG (OO-style)
-pub fn SvgDocument::render_line(
- self : SvgDocument,
- start : Point,
- end : Point,
- color : Color,
- thickness : Double,
-) -> SvgDocument {
- let element =
- $|
- self.add_element(element)
-}
-
-///|
-/// Render text to SVG (OO-style)
-pub fn SvgDocument::render_text(
- self : SvgDocument,
- text : String,
- pos : Point,
- size : Double,
- color : Color,
-) -> SvgDocument {
- let element =
- $|\{text}
- self.add_element(element)
-}
-
-///|
-/// Render an ellipse to SVG (OO-style)
-pub fn SvgDocument::render_ellipse(
- self : SvgDocument,
- center : Point,
- rx : Double,
- ry : Double,
- color : Color,
-) -> SvgDocument {
- let element =
- $|
- self.add_element(element)
-}
-
-///|
-/// Render a polygon to SVG (OO-style)
-pub fn SvgDocument::render_polygon(
- self : SvgDocument,
- points : Array[Point],
- color : Color,
-) -> SvgDocument {
- if points.length() < 3 {
- self // Can't render polygon with less than 3 points
- } else {
- let mut points_str = ""
- for i = 0; i < points.length(); i = i + 1 {
- let p = points[i]
- points_str = points_str + "\{p.x},\{p.y}"
- if i < points.length() - 1 {
- points_str = points_str + " "
- }
- }
- let element =
- $|
- self.add_element(element)
- }
-}
-
-///|
-/// Render a linear gradient to SVG (OO-style)
-pub fn SvgDocument::render_linear_gradient(
- self : SvgDocument,
- id : String,
- start : Point,
- end : Point,
- color1 : Color,
- color2 : Color,
-) -> SvgDocument {
- let gradient =
- $|"
- self.add_element(gradient)
-}
-
-///|
-/// Convert to SVG string (OO-style)
-pub fn SvgDocument::to_string(self : SvgDocument) -> String {
- (
- $|
- $|
- )
-}
+// SVG rendering backend - Image method only
///|
/// Sample an image at regular intervals and render to SVG (OO-style)
@@ -210,7 +8,7 @@ pub fn Image::render_image_to_svg(
height : Double,
samples : Int,
) -> String {
- let mut doc = new_svg(width, height)
+ let mut doc = @svg.new_svg(width, height)
let step = width / samples.to_double()
for i in 0.. SvgDocument {
+ { width, height, elements: [] }
+}
+
+///|
+/// Add an element to the SVG document (OO-style)
+pub fn SvgDocument::add_element(
+ self : SvgDocument,
+ element : String,
+) -> SvgDocument {
+ { ..self, elements: [..self.elements, element] }
+}
+
+///|
+/// Convert a color to SVG color string
+fn color_to_svg(c : @color.Color) -> String {
+ if c.a < 1.0 {
+ let r = (c.r * 255.0).to_int()
+ let g = (c.g * 255.0).to_int()
+ let b = (c.b * 255.0).to_int()
+ "rgba(\{r},\{g},\{b},\{c.a})"
+ } else {
+ @color.to_hex(c)
+ }
+}
+
+///|
+/// Convert a point to SVG coordinate string
+fn point_to_svg(p : @geometry.Point) -> String {
+ "\{p.x},\{p.y}"
+}
+
+///|
+/// Convert a path to SVG path data
+fn path_to_svg_data(path : @geometry.Path) -> String {
+ let mut data = ""
+ for segment in path.0 {
+ match segment {
+ MoveTo(p) => data = data + "M \{point_to_svg(p)} "
+ LineTo(p) => data = data + "L \{point_to_svg(p)} "
+ CurveTo(cp1, cp2, end) =>
+ data = data +
+ "C \{point_to_svg(cp1)} \{point_to_svg(cp2)} \{point_to_svg(end)} "
+ QCurveTo(cp, end) =>
+ data = data + "Q \{point_to_svg(cp)} \{point_to_svg(end)} "
+ EArcTo(rx, ry, rotation, large_arc, sweep, end) => {
+ let large_flag = if large_arc { "1" } else { "0" }
+ let sweep_flag = if sweep { "1" } else { "0" }
+ data = data +
+ "A \{rx} \{ry} \{rotation} \{large_flag} \{sweep_flag} \{point_to_svg(end)} "
+ }
+ Close => data = data + "Z "
+ }
+ }
+ data
+}
+
+///|
+/// Render a circle to SVG (OO-style)
+pub fn SvgDocument::render_circle(
+ self : SvgDocument,
+ center : @geometry.Point,
+ radius : Double,
+ color : @color.Color,
+) -> SvgDocument {
+ let element =
+ $|
+ self.add_element(element)
+}
+
+// ===== OBJECT-ORIENTED API METHODS =====
+
+///|
+/// Render a rectangle to SVG (OO-style)
+pub fn SvgDocument::render_rectangle(
+ self : SvgDocument,
+ x : Double,
+ y : Double,
+ width : Double,
+ height : Double,
+ color : @color.Color,
+) -> SvgDocument {
+ let element =
+ $|
+ self.add_element(element)
+}
+
+///|
+/// Render a path to SVG (OO-style)
+pub fn SvgDocument::render_path(
+ self : SvgDocument,
+ path : @geometry.Path,
+ color : @color.Color,
+) -> SvgDocument {
+ let data = path_to_svg_data(path)
+ let element =
+ $|
+ self.add_element(element)
+}
+
+///|
+/// Render a line to SVG (OO-style)
+pub fn SvgDocument::render_line(
+ self : SvgDocument,
+ start : @geometry.Point,
+ end : @geometry.Point,
+ color : @color.Color,
+ thickness : Double,
+) -> SvgDocument {
+ let element =
+ $|
+ self.add_element(element)
+}
+
+///|
+/// Render text to SVG (OO-style)
+pub fn SvgDocument::render_text(
+ self : SvgDocument,
+ text : String,
+ pos : @geometry.Point,
+ size : Double,
+ color : @color.Color,
+) -> SvgDocument {
+ let element =
+ $|\{text}
+ self.add_element(element)
+}
+
+///|
+/// Render an ellipse to SVG (OO-style)
+pub fn SvgDocument::render_ellipse(
+ self : SvgDocument,
+ center : @geometry.Point,
+ rx : Double,
+ ry : Double,
+ color : @color.Color,
+) -> SvgDocument {
+ let element =
+ $|
+ self.add_element(element)
+}
+
+///|
+/// Render a polygon to SVG (OO-style)
+pub fn SvgDocument::render_polygon(
+ self : SvgDocument,
+ points : Array[@geometry.Point],
+ color : @color.Color,
+) -> SvgDocument {
+ if points.length() < 3 {
+ self // Can't render polygon with less than 3 points
+ } else {
+ let mut points_str = ""
+ for i = 0; i < points.length(); i = i + 1 {
+ let p = points[i]
+ points_str = points_str + "\{p.x},\{p.y}"
+ if i < points.length() - 1 {
+ points_str = points_str + " "
+ }
+ }
+ let element =
+ $|
+ self.add_element(element)
+ }
+}
+
+///|
+/// Render a linear gradient to SVG (OO-style)
+pub fn SvgDocument::render_linear_gradient(
+ self : SvgDocument,
+ id : String,
+ start : @geometry.Point,
+ end : @geometry.Point,
+ color1 : @color.Color,
+ color2 : @color.Color,
+) -> SvgDocument {
+ let gradient =
+ $|"
+ self.add_element(gradient)
+}
+
+///|
+/// Convert to SVG string (OO-style)
+pub fn SvgDocument::to_string(self : SvgDocument) -> String {
+ (
+ $|
+ $|
+ )
+}
diff --git a/svg/types.mbt b/svg/types.mbt
new file mode 100644
index 0000000..3ec2786
--- /dev/null
+++ b/svg/types.mbt
@@ -0,0 +1,9 @@
+// Core types for SVG rendering
+
+///|
+/// SVG document structure
+pub struct SvgDocument {
+ width : Double
+ height : Double
+ elements : Array[String]
+} derive(Show)
diff --git a/svg_test.mbt b/svg_test.mbt
index 27fd822..ec36753 100644
--- a/svg_test.mbt
+++ b/svg_test.mbt
@@ -2,7 +2,7 @@
///|
test "SVG document creation" {
- let doc = @vg.new_svg(100.0, 200.0)
+ let doc = @svg.new_svg(100.0, 200.0)
if doc.width != 100.0 || doc.height != 200.0 {
fail("SVG document dimensions incorrect")
}
@@ -13,7 +13,7 @@ test "SVG document creation" {
///|
test "adding elements to SVG" {
- let doc = @vg.new_svg(100.0, 100.0)
+ let doc = @svg.new_svg(100.0, 100.0)
.add_element("")
.add_element("")
if doc.elements.length() != 2 {
@@ -23,7 +23,7 @@ test "adding elements to SVG" {
///|
test "circle rendering" (it : @test.Test) {
- let doc = @vg.new_svg(100.0, 100.0).render_circle(
+ let doc = @svg.new_svg(100.0, 100.0).render_circle(
@vg.Point::new(50.0, 50.0),
25.0,
@color.red(),
@@ -41,7 +41,7 @@ test "circle rendering" (it : @test.Test) {
///|
test "rectangle rendering" (it : @test.Test) {
- let doc = @vg.new_svg(200.0, 150.0).render_rectangle(
+ let doc = @svg.new_svg(200.0, 150.0).render_rectangle(
10.0,
20.0,
50.0,
@@ -56,7 +56,7 @@ test "rectangle rendering" (it : @test.Test) {
///|
test "path rendering" {
let path = @vg.Path::rect(0.0, 0.0, 10.0, 10.0)
- let doc = @vg.new_svg(100.0, 100.0).render_path(path, @color.green())
+ let doc = @svg.new_svg(100.0, 100.0).render_path(path, @color.green())
if doc.elements.length() != 1 {
fail("Should have one path element")
}
@@ -70,7 +70,7 @@ test "path rendering" {
test "line rendering" {
let start = @vg.Point::new(0.0, 0.0)
let end = @vg.Point::new(100.0, 50.0)
- let doc = @vg.new_svg(150.0, 100.0).render_line(
+ let doc = @svg.new_svg(150.0, 100.0).render_line(
start,
end,
@color.black(),
@@ -87,7 +87,7 @@ test "line rendering" {
///|
test "text rendering" {
- let doc = @vg.new_svg(200.0, 100.0).render_text(
+ let doc = @svg.new_svg(200.0, 100.0).render_text(
"Hello World",
@vg.Point::new(50.0, 30.0),
16.0,
@@ -104,7 +104,7 @@ test "text rendering" {
///|
test "SVG string generation" {
- let doc = @vg.new_svg(100.0, 100.0).render_circle(
+ let doc = @svg.new_svg(100.0, 100.0).render_circle(
@vg.Point::new(50.0, 50.0),
25.0,
@color.red(),
@@ -129,7 +129,7 @@ test "SVG string generation" {
///|
test "complex SVG document" {
- let doc = @vg.new_svg(200.0, 200.0)
+ let doc = @svg.new_svg(200.0, 200.0)
.render_rectangle(10.0, 10.0, 180.0, 180.0, @color.gray(0.9))
.render_circle(@vg.Point::new(100.0, 100.0), 50.0, @color.red())
.render_line(
@@ -166,7 +166,7 @@ test "image to SVG rendering" {
///|
test "color with alpha in SVG" {
let semi_transparent = @color.rgba(1.0, 0.0, 0.0, 0.5)
- let doc = @vg.new_svg(100.0, 100.0).render_circle(
+ let doc = @svg.new_svg(100.0, 100.0).render_circle(
@vg.Point::new(50.0, 50.0),
25.0,
semi_transparent,
@@ -181,7 +181,7 @@ test "color with alpha in SVG" {
///|
test "comprehensive svg rendering" (it : @test.Test) {
- let complex_doc = @vg.new_svg(300.0, 200.0)
+ let complex_doc = @svg.new_svg(300.0, 200.0)
.render_rectangle(10.0, 10.0, 280.0, 180.0, @color.gray(0.95)) // Background
.render_circle(@vg.Point::new(80.0, 60.0), 30.0, @color.red()) // Red circle
.render_ellipse(@vg.Point::new(150.0, 60.0), 25.0, 15.0, @color.blue()) // Blue ellipse
@@ -211,7 +211,7 @@ test "comprehensive svg rendering" (it : @test.Test) {
///|
test "svg string generation" (it : @test.Test) {
- let doc = @vg.new_svg(100.0, 100.0).render_circle(
+ let doc = @svg.new_svg(100.0, 100.0).render_circle(
@vg.Point::new(50.0, 50.0),
25.0,
@color.red(),
@@ -242,7 +242,7 @@ test "path rendering to svg" (it : @test.Test) {
)
.line_to(@vg.Point::new(10.0, 30.0))
.close_path()
- let doc = @vg.new_svg(120.0, 50.0).render_path(custom_path, @color.magenta())
+ let doc = @svg.new_svg(120.0, 50.0).render_path(custom_path, @color.magenta())
inspect(
doc,
content=(
@@ -282,7 +282,7 @@ test "gradient showcase" (it : @test.Test) {
///|
test "shape gallery" (it : @test.Test) {
- let doc = @vg.new_svg(500.0, 400.0)
+ let doc = @svg.new_svg(500.0, 400.0)
.render_rectangle(0.0, 0.0, 500.0, 400.0, @color.gray(0.98)) // Light background
.render_text(
"VG Shape Gallery",
@@ -362,7 +362,7 @@ test "shape gallery" (it : @test.Test) {
///|
test "path showcase" (it : @test.Test) {
- let doc = @vg.new_svg(400.0, 300.0)
+ let doc = @svg.new_svg(400.0, 300.0)
.render_rectangle(0.0, 0.0, 400.0, 300.0, @color.gray(0.95))
.render_text(
"Path Construction Showcase",
@@ -457,7 +457,7 @@ test "path showcase" (it : @test.Test) {
///|
test "svg rendering validation" (it : @test.Test) {
// Create a simple test to validate SVG structure
- let doc = @vg.new_svg(200.0, 100.0)
+ let doc = @svg.new_svg(200.0, 100.0)
.render_rectangle(10.0, 10.0, 180.0, 80.0, @color.gray(0.95))
.render_circle(@vg.Point::new(100.0, 50.0), 20.0, @color.red())
.render_text("SVG Test", @vg.Point::new(100.0, 30.0), 14.0, @color.black())
@@ -489,7 +489,7 @@ test "svg rendering validation" (it : @test.Test) {
///|
test "minimal svg rendering test" (it : @test.Test) {
// Create the simplest possible SVG to test basic rendering
- let doc = @vg.new_svg(300.0, 200.0)
+ let doc = @svg.new_svg(300.0, 200.0)
.render_rectangle(0.0, 0.0, 300.0, 200.0, @color.white()) // White background
.render_circle(@vg.Point::new(150.0, 100.0), 50.0, @color.red()) // Red circle in center
.render_rectangle(50.0, 50.0, 200.0, 100.0, @color.rgba(0.0, 0.0, 1.0, 0.5)) // Semi-transparent blue rect
@@ -507,7 +507,7 @@ test "minimal svg rendering test" (it : @test.Test) {
///|
test "simple rendering test" (it : @test.Test) {
// Ultra-simple SVG that should definitely render
- let doc = @vg.new_svg(100.0, 100.0).render_circle(
+ let doc = @svg.new_svg(100.0, 100.0).render_circle(
@vg.Point::new(50.0, 50.0),
30.0,
@color.red(),
@@ -520,7 +520,7 @@ test "simple rendering test" (it : @test.Test) {
///|
test "html wrapped svg test" (it : @test.Test) {
// Create an HTML file with embedded SVG for testing in browsers
- let doc = @vg.new_svg(400.0, 300.0)
+ let doc = @svg.new_svg(400.0, 300.0)
.render_rectangle(0.0, 0.0, 400.0, 300.0, @color.white())
.render_circle(@vg.Point::new(200.0, 150.0), 50.0, @color.red())
.render_rectangle(
diff --git a/types.mbt b/types.mbt
index ce807c8..d26a283 100644
--- a/types.mbt
+++ b/types.mbt
@@ -22,6 +22,11 @@ pub type Transform = @geometry.Transform
///|
pub type Color = @color.Color
+// Re-export svg type
+
+///|
+pub type SvgDocument = @svg.SvgDocument
+
///|
/// An image is a function from points to colors
pub(all) struct Image((Point) -> Color)