use crate::path::{
PathSlice, AttributeIndex, Attributes, LineCap, LineJoin, Side, Event,
builder::PathBuilder
};
use crate::path::path::BuilderWithAttributes;
use crate::math::{Point, Vector, point, vector};
use std::collections::VecDeque;
use std::f32::consts::PI;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct StrokerOptions {
pub start_cap: LineCap,
pub end_cap: LineCap,
pub line_join: LineJoin,
pub line_width: f32,
pub variable_line_width: Option<AttributeIndex>,
pub miter_limit: f32,
pub tolerance: f32,
}
#[derive(Clone)]
struct JoinData {
ctrl: Option<Point>,
position: Point,
half_width: f32,
line_join: LineJoin,
}
struct ComputedJoinData {
join: JoinData,
prev_attachment: Point,
next_attachment: Point,
single_attachment: bool,
folds: bool,
}
pub struct Stroker<'l> {
inner: Inner<'l>,
record: BuilderWithAttributes,
}
struct Inner<'l> {
options: StrokerOptions,
join_buffer: VecDeque<JoinData>,
computed_joins: VecDeque<ComputedJoinData>,
first_join: JoinData,
output: &'l mut dyn PathBuilder,
}
impl<'l> Stroker<'l> {
pub fn stroked_path_to_fill(
&mut self,
options: &StrokerOptions,
path: PathSlice,
) {
self.inner.options = *options;
let num_attributes = path.num_attributes();
for event in path.iter_with_attributes() {
if let Event::End { close, .. } = &event {
if !close {
}
self.record.event(event);
for rev_event in self.record.as_slice().reversed().with_attributes() {
self.inner.event(&rev_event, Side::Negative);
}
self.record.reset(num_attributes);
} else {
self.inner.event(&event, Side::Positive);
self.record.event(event);
}
}
}
}
impl<'l> Inner<'l> {
fn event(&mut self, event: &Event<(Point, Attributes), Point>, side: Side) {
match event {
Event::Begin { at } => {
let half_width: f32 = self.options.line_width * 0.5;
self.join_buffer.push_back(JoinData {
position: at.0,
ctrl: None,
half_width,
line_join: self.options.line_join,
});
self.output.begin(at.0, &[]);
}
Event::End { last, first, close } => {
}
Event::Line { to, .. } => {
let half_width: f32 = self.options.line_width * 0.5;
self.join_buffer.push_back(JoinData {
position: to.0,
ctrl: None,
half_width,
line_join: self.options.line_join,
});
}
_ => {
unimplemented!()
}
}
self.compute_joins(side);
}
fn compute_joins(&mut self, side: Side) {
while self.join_buffer.len() >= 3 {
let prev = &self.join_buffer[0];
let join = &self.join_buffer[1];
let next = &self.join_buffer[2];
compute_join(prev, join, next, side, &mut self.computed_joins);
self.join_buffer.pop_front();
}
}
}
fn compute_join(prev: &JoinData, join: &JoinData, next: &JoinData, side: Side, computed_joins: &mut VecDeque<ComputedJoinData>) {
let sign = match side {
Side::Positive => 1.0,
Side::Negative => -1.0,
};
let p0 = join.ctrl.unwrap_or(prev.position);
let p1 = next.ctrl.unwrap_or(next.position);
let path_v0 = (join.position - p0);
let path_v1 = (p1 - join.position);
let d0 = path_v0.length();
let d1 = path_v1.length();
let sin_vwidth_angle0 = (join.half_width - prev.half_width) / d0;
let sin_vwidth_angle1 = (next.half_width - join.half_width) / d1;
let mut vwidth_angle0 = sin_vwidth_angle0.asin();
let mut vwidth_angle1 = sin_vwidth_angle1.asin();
if !vwidth_angle0.is_finite() {
vwidth_angle0 = 0.0;
}
if !vwidth_angle1.is_finite() {
vwidth_angle1 = 0.0;
}
let edge_angle0 = path_v0.angle_from_x_axis().radians;
let edge_angle1 = path_v1.angle_from_x_axis().radians;
let normal_angle0 = edge_angle0 + sign * (PI * 0.5 + vwidth_angle0);
let normal_angle1 = edge_angle1 + sign * (PI * 0.5 + vwidth_angle1);
let normal0 = vector(normal_angle0.cos(), normal_angle0.sin());
let normal1 = vector(normal_angle1.cos(), normal_angle1.sin());
let attachment_prev = join.position + normal0 * join.half_width;
let attachment_next = join.position + normal1 * join.half_width;
let normal = compute_normal(v0, v1) * sign;
let inward = v0.cross(v1) * sign > 0.0;
let normal_same_side = (v0 + v1).dot(path_v0 + path_v1) >= 0.0;
let concave = inward && normal_same_side;
let kind = join.line_join;
let prev_is_line = join.ctrl.is_none();
let next_is_line = next.ctrl.is_none();
let miter_limit = 2.0;
let simple_convex = !concave
&& ((join.line_join == LineJoin::Miter || join.line_join == LineJoin::MiterClip)
&& !miter_limit_is_exceeded(normal, miter_limit));
let simple_concave = concave && prev_is_line && next_is_line;
if simple_convex || simple_concave {
let pos = join.position + normal * join.half_width;
computed_joins.push_back(ComputedJoinData {
join: join.clone(),
prev_attachment: pos,
next_attachment: pos,
single_attachment: true,
folds: false,
});
} else {
let folds = concave;
}
}
pub fn compute_normal(v1: Vector, v2: Vector) -> Vector {
let epsilon = 1e-4;
let n1 = vector(-v1.y, v1.x);
let v12 = v1 + v2;
if v12.square_length() < epsilon {
return vector(0.0, 0.0);
}
let tangent = v12.normalize();
let n = vector(-tangent.y, tangent.x);
let inv_len = n.dot(n1);
if inv_len.abs() < epsilon {
return n1;
}
n / inv_len
}
fn miter_limit_is_exceeded(normal: Vector, miter_limit: f32) -> bool {
normal.square_length() > miter_limit * miter_limit * 0.25
}