linesweeper 0.3.0

Robust sweep-line algorithm and two-dimensional boolean ops
Documentation
use std::path::PathBuf;

use clap::Parser;

use linesweeper::topology::Topology;
use linesweeper_util::svg_to_bezpaths;

#[derive(Parser)]
struct Args {
    input: PathBuf,
    output: PathBuf,

    #[arg(long)]
    epsilon: Option<f64>,
}

pub fn main() -> anyhow::Result<()> {
    let args = Args::parse();

    let input = std::fs::read_to_string(&args.input)?;
    let tree = usvg::Tree::from_str(&input, &usvg::Options::default())?;
    let contours = svg_to_bezpaths(&tree);

    let eps = args.epsilon.unwrap_or(0.1);
    let top =
        Topology::from_paths_binary(&contours[0], &contours[1..].iter().flatten().collect(), eps)?;

    let bbox = top.bounding_box();
    let min_x = bbox.min_x();
    let min_y = bbox.min_y();
    let max_x = bbox.max_x();
    let max_y = bbox.max_y();
    let pad = 8.0 + eps;
    let stroke_width = (max_y - min_y).max(max_x - max_y) / 512.0;
    let dot_radius = stroke_width * 1.5;
    let mut document = svg::Document::new().set(
        "viewBox",
        (
            min_x - pad,
            min_y - pad,
            max_x - min_x + 2.0 * pad,
            max_y - min_y + 2.0 * pad,
        ),
    );

    let text_size = "8px";

    for seg in top.segment_indices() {
        let p0 = &top.point(seg.first_half());
        let p1 = &top.point(seg.second_half());
        let (x0, y0) = (p0.x, p0.y);
        let (x1, y1) = (p1.x, p1.y);
        let c = svg::node::element::Circle::new()
            .set("r", dot_radius)
            .set("cy", y0)
            .set("cx", x0)
            .set("opacity", 0.5)
            .set("fill", "blue");
        document = document.add(c);

        let c = svg::node::element::Circle::new()
            .set("r", dot_radius)
            .set("cy", y1)
            .set("cx", x1)
            .set("opacity", 0.5)
            .set("fill", "blue");
        document = document.add(c);

        let data = svg::node::element::path::Data::new()
            .move_to((x0, y0))
            .line_to((x1, y1));
        let path = svg::node::element::Path::new()
            .set("stroke", "black")
            .set("stroke-width", stroke_width / 2.0)
            .set("stroke-opacity", "0.5")
            .set("d", data);
        document = document.add(path);

        let nx = y1 - y0;
        let ny = x0 - x1;
        let norm = ((nx * nx) + (ny * ny)).sqrt();

        let nx = nx / norm * 3.0 * dot_radius;
        let ny = ny / norm * 3.0 * dot_radius;

        let text = svg::node::element::Text::new(format!(
            "{:?}",
            top.winding_counter_clockwise(seg.first_half())
        ))
        .set("font-size", text_size)
        .set("text-anchor", "start")
        .set("x", (x0 + x1) / 2.0 + nx)
        .set("y", (y0 + y1) / 2.0 + ny);
        document = document.add(text);

        let text =
            svg::node::element::Text::new(format!("{:?}", top.winding_clockwise(seg.first_half())))
                .set("font-size", text_size)
                .set("text-anchor", "end")
                .set("x", (x0 + x1) / 2.0 - nx)
                .set("y", (y0 + y1) / 2.0 - ny);
        document = document.add(text);
    }

    svg::save(&args.output, &document)?;

    Ok(())
}