#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl Point {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CubicSegment {
MoveTo(Point),
LineTo(Point),
CurveTo { c1: Point, c2: Point, end: Point },
ClosePath,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CubicContour {
pub segments: Vec<CubicSegment>,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct BBox {
pub x_min: f32,
pub y_min: f32,
pub x_max: f32,
pub y_max: f32,
}
impl BBox {
pub fn width(&self) -> f32 {
(self.x_max - self.x_min).max(0.0)
}
pub fn height(&self) -> f32 {
(self.y_max - self.y_min).max(0.0)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CubicOutline {
pub contours: Vec<CubicContour>,
pub bounds: BBox,
}
impl CubicOutline {
pub fn is_empty(&self) -> bool {
self.contours.is_empty()
}
pub(crate) fn recompute_bounds(&mut self) {
let mut x_min = f32::INFINITY;
let mut y_min = f32::INFINITY;
let mut x_max = f32::NEG_INFINITY;
let mut y_max = f32::NEG_INFINITY;
let mut any = false;
for c in &self.contours {
for s in &c.segments {
match s {
CubicSegment::MoveTo(p) | CubicSegment::LineTo(p) => {
any = true;
x_min = x_min.min(p.x);
x_max = x_max.max(p.x);
y_min = y_min.min(p.y);
y_max = y_max.max(p.y);
}
CubicSegment::CurveTo { c1, c2, end } => {
any = true;
for p in [c1, c2, end] {
x_min = x_min.min(p.x);
x_max = x_max.max(p.x);
y_min = y_min.min(p.y);
y_max = y_max.max(p.y);
}
}
CubicSegment::ClosePath => {}
}
}
}
self.bounds = if any {
BBox {
x_min,
y_min,
x_max,
y_max,
}
} else {
BBox::default()
};
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_outline() {
let o = CubicOutline::default();
assert!(o.is_empty());
assert_eq!(o.bounds, BBox::default());
}
#[test]
fn bounds_track_curve_control_points() {
let mut o = CubicOutline::default();
o.contours.push(CubicContour {
segments: vec![
CubicSegment::MoveTo(Point::new(0.0, 0.0)),
CubicSegment::CurveTo {
c1: Point::new(50.0, 100.0),
c2: Point::new(50.0, -100.0),
end: Point::new(100.0, 0.0),
},
CubicSegment::ClosePath,
],
});
o.recompute_bounds();
assert_eq!(o.bounds.x_min, 0.0);
assert_eq!(o.bounds.x_max, 100.0);
assert_eq!(o.bounds.y_min, -100.0);
assert_eq!(o.bounds.y_max, 100.0);
}
}