lyon_algorithms 1.0.2

2D Path manipulation/transformation algorithms.
Documentation
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;

/// Parameters for the tessellator.
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct StrokerOptions {
    /// What cap to use at the start of each sub-path.
    ///
    /// Default value: `LineCap::Butt`.
    pub start_cap: LineCap,

    /// What cap to use at the end of each sub-path.
    ///
    /// Default value: `LineCap::Butt`.
    pub end_cap: LineCap,

    /// See the SVG specification.
    ///
    /// Default value: `LineJoin::Miter`.
    pub line_join: LineJoin,

    /// Line width
    ///
    /// Default value: `StrokeOptions::DEFAULT_LINE_WIDTH`.
    pub line_width: f32,

    /// Index of a custom attribute defining a per-vertex
    /// factor to modulate the line width.
    ///
    /// Default value: `None`.
    pub variable_line_width: Option<AttributeIndex>,

    /// See the SVG specification.
    ///
    /// Must be greater than or equal to 1.0.
    /// Default value: `StrokeOptions::DEFAULT_MITER_LIMIT`.
    pub miter_limit: f32,

    /// Maximum allowed distance to the path when building an approximation.
    ///
    /// See [Flattening and tolerance](index.html#flattening-and-tolerance).
    /// Default value: `StrokeOptions::DEFAULT_TOLERANCE`.
    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 {
                    // TODO: caps.
                }

                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();

    // Extra angle produced by the varying stroke width.
    // sin(vwidth_angle) = (hw1 - hw0) / d
    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 the distance between the joins (d) is smaller than either of the half
    // widths, we end up in a situation where sin_vwidth_angle is not in [-1, 1],
    // which causes vwidth_anfle to be NaN. Prevent that here for safety's sake
    // but it would be better to handle that earlier to do something that looks
    // more plausible with round joins.
    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 forward = v0.dot(v1) > 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; // TODO

    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;

    }
}

// TODO: copied from lyon_tessellation::math_utils
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
}