#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineCap {
#[default]
Butt,
Round,
Square,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineJoin {
#[default]
Miter,
Round,
Bevel,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StrokeStyle {
pub width: f32,
pub cap: LineCap,
pub join: LineJoin,
pub miter_limit: f32,
}
impl Default for StrokeStyle {
fn default() -> Self {
Self {
width: 1.0,
cap: LineCap::default(),
join: LineJoin::default(),
miter_limit: 4.0,
}
}
}
impl StrokeStyle {
pub fn new(width: f32) -> Self {
Self {
width,
..Default::default()
}
}
pub fn with_width(mut self, width: f32) -> Self {
self.width = width;
self
}
pub fn with_cap(mut self, cap: LineCap) -> Self {
self.cap = cap;
self
}
pub fn with_join(mut self, join: LineJoin) -> Self {
self.join = join;
self
}
pub fn with_miter_limit(mut self, limit: f32) -> Self {
self.miter_limit = limit;
self
}
pub fn is_visible(&self) -> bool {
self.width > 0.0 && self.width.is_finite()
}
pub fn can_draw(&self) -> bool {
self.width >= 0.0 && self.width.is_finite()
}
pub fn max_extent(&self) -> f32 {
if !self.is_visible() {
return 0.0;
}
let half = self.width * 0.5;
match self.join {
LineJoin::Miter => half * self.miter_limit.max(1.0),
LineJoin::Round | LineJoin::Bevel => half,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_the_svg_conventions() {
let s = StrokeStyle::default();
assert_eq!(s.cap, LineCap::Butt);
assert_eq!(s.join, LineJoin::Miter);
assert_eq!(s.miter_limit, 4.0);
}
#[test]
fn non_positive_and_non_finite_widths_are_invisible() {
assert!(!StrokeStyle::new(0.0).is_visible());
assert!(!StrokeStyle::new(-1.0).is_visible());
assert!(StrokeStyle::new(0.0).can_draw());
assert!(!StrokeStyle::new(-1.0).can_draw());
assert!(!StrokeStyle::new(f32::NAN).can_draw());
assert!(!StrokeStyle::new(f32::NAN).is_visible());
assert!(!StrokeStyle::new(f32::INFINITY).is_visible());
assert!(StrokeStyle::new(0.5).is_visible());
}
#[test]
fn miter_joins_reach_further_than_the_half_width() {
let w = 4.0;
let miter = StrokeStyle::new(w).with_join(LineJoin::Miter);
assert_eq!(miter.max_extent(), 2.0 * 4.0);
for join in [LineJoin::Round, LineJoin::Bevel] {
assert_eq!(StrokeStyle::new(w).with_join(join).max_extent(), 2.0);
}
}
#[test]
fn a_miter_limit_below_one_cannot_shrink_the_extent() {
let s = StrokeStyle::new(4.0).with_miter_limit(0.1);
assert_eq!(s.max_extent(), 2.0);
}
#[test]
fn invisible_strokes_have_no_extent() {
assert_eq!(StrokeStyle::new(0.0).max_extent(), 0.0);
assert_eq!(StrokeStyle::new(f32::NAN).max_extent(), 0.0);
}
}