diff --git a/image.mbt b/image.mbt index 3beca8f..b881d7a 100644 --- a/image.mbt +++ b/image.mbt @@ -330,3 +330,27 @@ pub fn Image::checkerboard( } } } + +///| +/// Sample an image at regular intervals and render to SVG (OO-style) +pub fn Image::render_image_to_svg( + self : Image, + width : Double, + height : Double, + samples : Int, +) -> String { + let mut doc = new_svg(width, height) + let step = width / samples.to_double() + for i in 0.. 0.0 { // Only render non-transparent pixels + doc = doc.render_rectangle(x, y, step, step, color) + } + } + } + doc.to_string() +} 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/svg.mbt b/svg.mbt index e774777..6461f3e 100644 --- a/svg.mbt +++ b/svg.mbt @@ -1,227 +1,5 @@ -// SVG rendering backend +// SVG rendering backend - Re-exports from svg package -///| -/// 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 { - ( - $| - $| - $|\{self.elements.map(e => " " + e).join("\n")} - $| - ) -} - -///| -/// Sample an image at regular intervals and render to SVG (OO-style) -pub fn Image::render_image_to_svg( - self : Image, - width : Double, - height : Double, - samples : Int, -) -> String { - let mut doc = new_svg(width, height) - let step = width / samples.to_double() - for i in 0.. 0.0 { // Only render non-transparent pixels - doc = doc.render_rectangle(x, y, step, step, color) - } - } - } - doc.to_string() -} +// Re-export SVG types and functions +pub type SvgDocument = @svg.SvgDocument +pub let new_svg : (Double, Double) -> SvgDocument = @svg.new_svg diff --git a/svg/moon.pkg.json b/svg/moon.pkg.json new file mode 100644 index 0000000..5cef213 --- /dev/null +++ b/svg/moon.pkg.json @@ -0,0 +1,7 @@ +{ + "is-main": false, + "import": [ + "bobzhang/vg/geometry", + "bobzhang/vg/color" + ] +} \ No newline at end of file diff --git a/svg/svg.mbt b/svg/svg.mbt new file mode 100644 index 0000000..155ab60 --- /dev/null +++ b/svg/svg.mbt @@ -0,0 +1,236 @@ +// SVG rendering backend + +// Import types from parent packages +pub type Point = @geometry.Point +pub type Path = @geometry.Path +pub type PathSegment = @geometry.PathSegment +pub type Color = @color.Color + +// Import Image type from parent +pub(all) struct Image((Point) -> Color) + +///| +/// 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 { + @geometry.MoveTo(p) => data = data + "M \{point_to_svg(p)} " + @geometry.LineTo(p) => data = data + "L \{point_to_svg(p)} " + @geometry.CurveTo(cp1, cp2, end) => + data = data + + "C \{point_to_svg(cp1)} \{point_to_svg(cp2)} \{point_to_svg(end)} " + @geometry.QCurveTo(cp, end) => + data = data + "Q \{point_to_svg(cp)} \{point_to_svg(end)} " + @geometry.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)} " + } + @geometry.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 { + ( + $| + $| + $|\{self.elements.map(e => " " + e).join("\n")} + $| + ) +} + +///| +/// Sample an image at regular intervals and render to SVG (OO-style) +pub fn Image::render_image_to_svg( + self : Image, + width : Double, + height : Double, + samples : Int, +) -> String { + let mut doc = new_svg(width, height) + let step = width / samples.to_double() + for i in 0.. 0.0 { // Only render non-transparent pixels + doc = doc.render_rectangle(x, y, step, step, color) + } + } + } + doc.to_string() +} \ No newline at end of file