roughr 0.12.0

Generate Hand Sketched 2D Drawings
Documentation
use std::fmt::Display;
use std::ops::MulAssign;

use euclid::default::Point2D;
use euclid::Trig;
use num_traits::{Float, FromPrimitive};
use points_on_curve::{points_on_bezier_curves, simplify};
use svg_path_ops::{absolutize, normalize};
use svgtypes::{PathParser, PathSegment};

use crate::core::{_c, _cc};

pub fn points_on_path<F>(
    path: String,
    tolerance: Option<F>,
    distance: Option<F>,
) -> Vec<Vec<Point2D<F>>>
where
    F: FromPrimitive + Trig + Float + MulAssign + Display,
{
    let path_parser = PathParser::from(path.as_ref());
    let path_segments: Vec<PathSegment> = path_parser.flatten().collect();
    let normalized_segments = normalize(absolutize(path_segments.iter()));

    generate_points(tolerance, distance, normalized_segments)
}

pub fn points_on_segments<F>(
    path_segments: Vec<PathSegment>,
    tolerance: Option<F>,
    distance: Option<F>,
) -> Vec<Vec<Point2D<F>>>
where
    F: FromPrimitive + Trig + Float + MulAssign + Display,
{
    let normalized_segments = normalize(absolutize(path_segments.iter()));
    generate_points(tolerance, distance, normalized_segments)
}

fn generate_points<F>(
    tolerance: Option<F>,
    distance: Option<F>,
    normalized_segments: impl Iterator<Item = PathSegment>,
) -> Vec<Vec<euclid::Point2D<F, euclid::UnknownUnit>>>
where
    F: FromPrimitive + Trig + Float + MulAssign + Display,
{
    let mut sets: Vec<Vec<Point2D<F>>> = vec![];
    let mut current_points: Vec<Point2D<F>> = vec![];
    let mut start = Point2D::new(_c::<F>(0.0), _c::<F>(0.0));
    let mut pending_curve: Vec<Point2D<F>> = vec![];

    let append_pending_curve =
        |current_points: &mut Vec<Point2D<F>>, pending_curve: &mut Vec<Point2D<F>>| {
            if pending_curve.len() >= 4 {
                current_points.append(&mut points_on_bezier_curves(
                    &pending_curve[..],
                    tolerance.unwrap_or(_c(0.0)),
                    None,
                ));
            }
            pending_curve.clear();
        };

    let mut append_pending_points =
        |current_points: &mut Vec<Point2D<F>>, pending_curve: &mut Vec<Point2D<F>>| {
            {
                append_pending_curve(current_points, pending_curve);
            }
            if !current_points.is_empty() {
                sets.push(current_points.clone());
                current_points.clear();
            }
        };

    for segment in normalized_segments {
        match segment {
            PathSegment::MoveTo { abs: true, x, y } => {
                append_pending_points(&mut current_points, &mut pending_curve);
                start = Point2D::new(_cc::<F>(x), _cc::<F>(y));
                current_points.push(start);
            }
            PathSegment::LineTo { abs: true, x, y } => {
                append_pending_curve(&mut current_points, &mut pending_curve);
                current_points.push(Point2D::new(_cc::<F>(x), _cc::<F>(y)));
            }
            PathSegment::CurveTo { abs: true, x1, y1, x2, y2, x, y } => {
                if pending_curve.is_empty() {
                    let last_point = if !current_points.is_empty() {
                        current_points.last().unwrap()
                    } else {
                        &start
                    };
                    pending_curve.push(*last_point);
                }
                pending_curve.push(Point2D::new(_cc::<F>(x1), _cc::<F>(y1)));
                pending_curve.push(Point2D::new(_cc::<F>(x2), _cc::<F>(y2)));
                pending_curve.push(Point2D::new(_cc::<F>(x), _cc::<F>(y)));
            }
            PathSegment::ClosePath { abs: true } => {
                append_pending_curve(&mut current_points, &mut pending_curve);
                current_points.push(start);
            }
            _ => panic!("unexpected  path segment"),
        }
    }

    append_pending_points(&mut current_points, &mut pending_curve);

    if let Some(dst) = distance {
        let mut out = vec![];
        for set in sets.iter() {
            let simplified_set = simplify(set, dst);
            if !simplified_set.is_empty() {
                out.push(simplified_set);
            }
        }
        out
    } else {
        sets
    }
}