use alpha2_model::{LineCap, LineJoin};
use kurbo::{Affine, BezPath, Cap, Join, Stroke, StrokeOpts};
const STROKE_TOLERANCE: f64 = 0.05;
const MAX_DASH_REPEATS: f64 = 100_000.0;
fn control_length(path: &BezPath) -> f64 {
let mut total = 0.0;
let mut last = kurbo::Point::ZERO;
let mut start = kurbo::Point::ZERO;
for element in path.elements() {
let points: &[kurbo::Point] = match element {
kurbo::PathEl::MoveTo(p) => {
last = *p;
start = *p;
continue;
}
kurbo::PathEl::LineTo(p) => std::slice::from_ref(p),
kurbo::PathEl::QuadTo(a, b) => &[*a, *b],
kurbo::PathEl::CurveTo(a, b, c) => &[*a, *b, *c],
kurbo::PathEl::ClosePath => std::slice::from_ref(&start),
};
for point in points {
total += crate::affine::distance(last, *point);
last = *point;
}
}
total
}
#[derive(Clone, Debug, PartialEq)]
pub struct StrokeStyle {
pub width: f64,
pub join: LineJoin,
pub miter_limit: f64,
pub cap: LineCap,
pub dash: Vec<f64>,
pub dash_offset: f64,
}
#[must_use]
pub fn stroke_outline(path: &BezPath, style: &StrokeStyle, to_device: Affine) -> BezPath {
if style.width.is_nan() || style.width <= 0.0 || path.elements().is_empty() {
return BezPath::new();
}
let join = match style.join {
LineJoin::Miter => Join::Miter,
LineJoin::Round => Join::Round,
LineJoin::Bevel => Join::Bevel,
};
let cap = match style.cap {
LineCap::Butt => Cap::Butt,
LineCap::Round => Cap::Round,
LineCap::Square => Cap::Square,
};
let mut stroke = Stroke::new(style.width)
.with_join(join)
.with_miter_limit(style.miter_limit.max(1.0))
.with_caps(cap);
let pattern_length: f64 = style.dash.iter().sum();
let repeats = control_length(path) / pattern_length;
if pattern_length > 0.0 && repeats <= MAX_DASH_REPEATS {
let mut pattern = style.dash.clone();
if pattern.len() % 2 == 1 {
pattern.extend_from_slice(&style.dash);
}
stroke = stroke.with_dashes(style.dash_offset, pattern);
}
let scale = crate::affine::max_scale(to_device).max(1e-9);
let outline = kurbo::stroke(
path.iter(),
&stroke,
&StrokeOpts::default(),
STROKE_TOLERANCE / scale,
);
to_device * outline
}