lyon_extra 1.1.0

Various optional utilities for the lyon crate.
Documentation
use path::math::Point;
use path::PathEvent;
use path::{Path, PathSlice};

use std::{println, vec::Vec, panic};

pub type Polygons = Vec<Vec<Point>>;
pub type PolygonsRef<'a> = &'a [Vec<Point>];

pub fn path_to_polygons(path: PathSlice) -> Polygons {
    let mut polygons = Vec::new();
    let mut poly = Vec::new();
    for evt in path {
        match evt {
            PathEvent::Begin { at } => {
                if !poly.is_empty() {
                    polygons.push(poly);
                }
                poly = vec![at];
            }
            PathEvent::Line { to, .. } => {
                poly.push(to);
            }
            PathEvent::End { .. } => {
                if !poly.is_empty() {
                    polygons.push(poly);
                }
                poly = Vec::new();
            }
            _ => {
                println!(
                    " -- path_to_polygons: warning! Unsupported event type {evt:?}"
                );
            }
        }
    }
    polygons
}

pub fn polygons_to_path(polygons: PolygonsRef) -> Path {
    let mut builder = Path::builder().flattened(0.05);
    for poly in polygons.iter() {
        let mut poly_iter = poly.iter();
        builder.begin(*poly_iter.next().unwrap());
        for v in poly_iter {
            builder.line_to(*v);
        }
        builder.close();
    }
    builder.build()
}

pub fn find_reduced_test_case<F: Fn(Path) -> bool + panic::UnwindSafe + panic::RefUnwindSafe>(
    path: PathSlice,
    cb: &F,
) -> Path {
    let mut polygons = path_to_polygons(path);

    println!(" -- removing sub-paths...");

    polygons = find_reduced_test_case_sp(polygons, cb);

    println!(" -- removing vertices...");

    for p in 0..polygons.len() {
        let mut v = 0;
        loop {
            if v >= polygons[p].len() || polygons[p].len() <= 3 {
                break;
            }

            let mut cloned = polygons.clone();
            cloned[p].remove(v);

            let path = polygons_to_path(&cloned);

            let failed = panic::catch_unwind(|| cb(path)).unwrap_or(true);

            if failed {
                polygons = cloned;
                continue;
            }

            v += 1;
        }
    }

    let path = polygons_to_path(&polygons);
    println!(" ----------- reduced test case: -----------\n\n");
    println!("#[test]");
    println!("fn reduced_test_case() {{");
    println!("    let mut builder = Path::builder();\n");
    for poly in &polygons {
        let mut poly_iter = poly.iter();
        let pos = *poly_iter.next().unwrap();
        println!("    builder.begin(point({}, {}));", pos.x, pos.y);
        for pos in poly_iter {
            println!("    builder.line_to(point({}, {}));", pos.x, pos.y);
        }
        println!("    builder.close();\n");
    }
    println!("    test_path(builder.build().as_slice());\n");
    println!("    // SVG path syntax:");
    println!("    // \"{path:?}\"");
    println!("}}\n\n");

    path
}

fn find_reduced_test_case_sp<F>(mut polygons: Polygons, cb: &F) -> Polygons
where
    F: Fn(Path) -> bool + panic::UnwindSafe + panic::RefUnwindSafe,
{
    let mut i = 0;
    loop {
        if i >= polygons.len() {
            return polygons;
        }

        let mut cloned = polygons.clone();
        cloned.remove(i);
        let path = polygons_to_path(&cloned);

        let failed = panic::catch_unwind(|| cb(path)).unwrap_or(true);

        if failed {
            polygons = cloned;
            continue;
        }

        i += 1;
    }
}