///|
/// SVG Path Segment representation
pub(all) enum PathSegment {
MoveTo(Double, Double)
LineTo(Double, Double)
QuadTo(x1~ : Double, y1~ : Double, x~ : Double, y~ : Double)
CubicTo(
x1~ : Double,
y1~ : Double,
x2~ : Double,
y2~ : Double,
x~ : Double,
y~ : Double
)
ClosePath
} derive(Eq, Debug, ToJson)
///|
/// Programmatic SVG Path Builder
pub(all) struct Path {
segments : Array[PathSegment]
} derive(Eq, Debug, ToJson)
///|
/// Create a new empty Path
pub fn Path::new() -> Path {
{ segments: [] }
}
///|
/// Move to a point
pub fn Path::move_to(self : Path, x : Double, y : Double) -> Path {
self.segments.push(MoveTo(x, y))
self
}
///|
/// Draw a line to a point
pub fn Path::line_to(self : Path, x : Double, y : Double) -> Path {
self.segments.push(LineTo(x, y))
self
}
///|
/// Draw a quadratic bezier curve
pub fn Path::quad_to(
self : Path,
x1 : Double,
y1 : Double,
x : Double,
y : Double,
) -> Path {
self.segments.push(QuadTo(x1~, y1~, x~, y~))
self
}
///|
/// Draw a cubic bezier curve
pub fn Path::cubic_to(
self : Path,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
x : Double,
y : Double,
) -> Path {
self.segments.push(CubicTo(x1~, y1~, x2~, y2~, x~, y~))
self
}
///|
/// Close the current path
pub fn Path::close(self : Path) -> Path {
self.segments.push(ClosePath)
self
}
///|
/// Convert path to SVG path string (d attribute value)
pub fn Path::to_svg_d(self : Path) -> String {
let sb = StringBuilder()
for i, seg in self.segments {
if i > 0 {
sb.write_string(" ")
}
match seg {
MoveTo(x, y) => sb.write_string("M \{x} \{y}")
LineTo(x, y) => sb.write_string("L \{x} \{y}")
QuadTo(x1~, y1~, x~, y~) => sb.write_string("Q \{x1} \{y1} \{x} \{y}")
CubicTo(x1~, y1~, x2~, y2~, x~, y~) =>
sb.write_string("C \{x1} \{y1} \{x2} \{y2} \{x} \{y}")
ClosePath => sb.write_string("Z")
}
}
sb.to_string()
}
///|
/// Transform all path coordinates using a 2D affine matrix
pub fn Path::transform(self : Path, matrix : Matrix2D) -> Path {
let new_segments = Array::new(capacity=self.segments.length())
for seg in self.segments {
let new_seg = match seg {
MoveTo(x, y) => {
let (nx, ny) = matrix.transform_point(x, y)
PathSegment::MoveTo(nx, ny)
}
LineTo(x, y) => {
let (nx, ny) = matrix.transform_point(x, y)
PathSegment::LineTo(nx, ny)
}
QuadTo(x1~, y1~, x~, y~) => {
let (nx1, ny1) = matrix.transform_point(x1, y1)
let (nx, ny) = matrix.transform_point(x, y)
PathSegment::QuadTo(x1=nx1, y1=ny1, x=nx, y=ny)
}
CubicTo(x1~, y1~, x2~, y2~, x~, y~) => {
let (nx1, ny1) = matrix.transform_point(x1, y1)
let (nx2, ny2) = matrix.transform_point(x2, y2)
let (nx, ny) = matrix.transform_point(x, y)
PathSegment::CubicTo(x1=nx1, y1=ny1, x2=nx2, y2=ny2, x=nx, y=ny)
}
ClosePath => PathSegment::ClosePath
}
new_segments.push(new_seg)
}
{ segments: new_segments }
}
///|
/// Creates a rectangular path
pub fn Path::rect(w : Double, h : Double, x? : Double, y? : Double) -> Path {
let px = x.unwrap_or(0.0)
let py = y.unwrap_or(0.0)
Path::new()
.move_to(px, py)
.line_to(px + w, py)
.line_to(px + w, py + h)
.line_to(px, py + h)
.close()
}
///|
/// Creates a circular path (approximated with 4 cubic Bezier curves)
pub fn Path::circle(r : Double, cx? : Double, cy? : Double) -> Path {
let pcx = cx.unwrap_or(0.0)
let pcy = cy.unwrap_or(0.0)
// kappa = 4 * (sqrt(2) - 1) / 3 approx 0.5522847498
let kappa = 0.5522847498
let ox = r * kappa
let oy = r * kappa
Path::new()
.move_to(pcx - r, pcy)
.cubic_to(pcx - r, pcy - oy, pcx - ox, pcy - r, pcx, pcy - r)
.cubic_to(pcx + ox, pcy - r, pcx + r, pcy - oy, pcx + r, pcy)
.cubic_to(pcx + r, pcy + oy, pcx + ox, pcy + r, pcx, pcy + r)
.cubic_to(pcx - ox, pcy + r, pcx - r, pcy + oy, pcx - r, pcy)
.close()
}
///|
/// Creates a star shape path
pub fn Path::star(
cx : Double,
cy : Double,
spikes : Int,
inner_radius : Double,
outer_radius : Double,
) -> Path {
let path = Path::new()
let rot = @math.PI / 2.0 * 3.0
let step = @math.PI / spikes.to_double()
let x0 = cx + @math.cos(rot) * outer_radius
let y0 = cy + @math.sin(rot) * outer_radius
let _ = path.move_to(x0, y0)
for i in 0.. Path {
let path = Path::new()
let rot = 0.0
let step = 2.0 * @math.PI / petals.to_double()
let x0 = cx + @math.cos(rot) * inner_radius
let y0 = cy + @math.sin(rot) * inner_radius
let _ = path.move_to(x0, y0)
for i in 0..