// Path construction and manipulation utilities
///|
/// Create an empty path
pub fn Path::empty() -> Path {
Path([])
}
///|
/// Move to a point
pub fn Path::move_to(self : Path, p : Point) -> Path {
Path([..self.0, MoveTo(p)])
}
///|
/// Line to a point
pub fn Path::line_to(self : Path, p : Point) -> Path {
Path([..self.0, LineTo(p)])
}
///|
/// Cubic bezier curve to a point
pub fn Path::curve_to(
self : Path,
cp1 : Point,
cp2 : Point,
end : Point,
) -> Path {
Path([..self.0, CurveTo(cp1, cp2, end)])
}
///|
/// Quadratic bezier curve to a point
pub fn Path::qcurve_to(self : Path, cp : Point, end : Point) -> Path {
Path([..self.0, QCurveTo(cp, end)])
}
///|
/// Elliptical arc to a point
pub fn Path::earc_to(
self : Path,
rx : Double,
ry : Double,
rotation : Double,
large_arc : Bool,
sweep : Bool,
end : Point,
) -> Path {
Path([..self.0, EArcTo(rx, ry, rotation, large_arc, sweep, end)])
}
///|
/// Close the path
pub fn Path::close_path(self : Path) -> Path {
Path([..self.0, Close])
}
///|
/// Create a rectangle path
pub fn Path::rect(
x : Double,
y : Double,
width : Double,
height : Double,
) -> Path {
let top_left = Point::Point(x, y)
let top_right = Point::Point(x + width, y)
let bottom_right = Point::Point(x + width, y + height)
let bottom_left = Point::Point(x, y + height)
Path::empty()
.move_to(top_left)
.line_to(top_right)
.line_to(bottom_right)
.line_to(bottom_left)
.close_path()
}
///|
/// Create a circle path (approximated with bezier curves)
pub fn Path::circle(center : Point, radius : Double) -> Path {
let kappa = 0.5522847498 // Magic number for circle approximation
let offset = radius * kappa
let top = Point::Point(center.x, center.y - radius)
let right = Point::Point(center.x + radius, center.y)
let bottom = Point::Point(center.x, center.y + radius)
let left = Point::Point(center.x - radius, center.y)
Path::empty()
.move_to(top)
.curve_to(
Point(center.x + offset, center.y - radius),
Point(center.x + radius, center.y - offset),
right,
)
.curve_to(
Point(center.x + radius, center.y + offset),
Point(center.x + offset, center.y + radius),
bottom,
)
.curve_to(
Point(center.x - offset, center.y + radius),
Point(center.x - radius, center.y + offset),
left,
)
.curve_to(
Point(center.x - radius, center.y - offset),
Point(center.x - offset, center.y - radius),
top,
)
.close_path()
}
///|
/// Create an ellipse path
pub fn Path::ellipse(center : Point, rx : Double, ry : Double) -> Path {
let kappa = 0.5522847498
let offset_x = rx * kappa
let offset_y = ry * kappa
let top = Point::Point(center.x, center.y - ry)
let right = Point::Point(center.x + rx, center.y)
let bottom = Point::Point(center.x, center.y + ry)
let left = Point::Point(center.x - rx, center.y)
Path::empty()
.move_to(top)
.curve_to(
Point(center.x + offset_x, center.y - ry),
Point(center.x + rx, center.y - offset_y),
right,
)
.curve_to(
Point(center.x + rx, center.y + offset_y),
Point(center.x + offset_x, center.y + ry),
bottom,
)
.curve_to(
Point(center.x - offset_x, center.y + ry),
Point(center.x - rx, center.y + offset_y),
left,
)
.curve_to(
Point(center.x - rx, center.y - offset_y),
Point(center.x - offset_x, center.y - ry),
top,
)
.close_path()
}
///|
/// Get the bounding box of a path (approximate)
pub fn Path::bounds(self : Path) -> Box? {
let mut min_x = 1000000.0
let mut min_y = 1000000.0
let mut max_x = -1000000.0
let mut max_y = -1000000.0
let mut has_points = false
fn update_bounds(p : Point) {
has_points = true
if p.x < min_x {
min_x = p.x
}
if p.x > max_x {
max_x = p.x
}
if p.y < min_y {
min_y = p.y
}
if p.y > max_y {
max_y = p.y
}
}
for segment in self.0 {
match segment {
MoveTo(p) | LineTo(p) => update_bounds(p)
CurveTo(cp1, cp2, end) => {
update_bounds(cp1)
update_bounds(cp2)
update_bounds(end)
}
QCurveTo(cp, end) => {
update_bounds(cp)
update_bounds(end)
}
EArcTo(_, _, _, _, _, end) => update_bounds(end) // Simplified bounds for arcs
Close => ()
}
}
if has_points {
Some({ min_x, min_y, max_x, max_y })
} else {
None
}
}
///|
/// Create a smooth cubic curve that connects smoothly to the previous segment
pub fn Path::smooth_ccurve_to(self : Path, cp2 : Point, end : Point) -> Path {
// reflect the previous cubic control point for a smooth join
let cp1 = match self.0 {
[.., CurveTo(_, prev_cp2, prev_end)] =>
Point(2.0 * prev_end.x - prev_cp2.x, 2.0 * prev_end.y - prev_cp2.y)
_ => cp2
}
Path([..self.0, CurveTo(cp1, cp2, end)])
}
///|
/// Create a smooth quadratic curve that connects smoothly to the previous segment
pub fn Path::smooth_qcurve_to(self : Path, end : Point) -> Path {
// reflect the previous quadratic control point for a smooth join
let cp = match self.0 {
[.., QCurveTo(prev_cp, prev_end)] =>
Point(2.0 * prev_end.x - prev_cp.x, 2.0 * prev_end.y - prev_cp.y)
_ => end
}
Path([..self.0, QCurveTo(cp, end)])
}
///|
/// Transform a path using a transformation matrix
pub fn Path::transform(self : Path, t : Transform) -> Path {
Path(
[
for segment in self.0 => {
match segment {
MoveTo(p) => MoveTo(apply(t, p))
LineTo(p) => LineTo(apply(t, p))
CurveTo(cp1, cp2, end) =>
CurveTo(apply(t, cp1), apply(t, cp2), apply(t, end))
QCurveTo(cp, end) => QCurveTo(apply(t, cp), apply(t, end))
EArcTo(rx, ry, rotation, large_arc, sweep, end) =>
EArcTo(rx, ry, rotation, large_arc, sweep, apply(t, end))
Close => Close
}
}
],
)
}