use crate::{Point, Polyline, DEFAULT_TOLERANCE};
use kurbo::{BezPath, PathEl, PathSeg};
pub trait IntoBezPathTolerance {
fn into_bezpath_with_tolerance(self, tolerance: f64) -> BezPath;
}
pub trait IntoBezPath {
fn into_bezpath(self) -> BezPath;
}
impl<T: IntoBezPathTolerance> IntoBezPath for T {
fn into_bezpath(self) -> BezPath {
<Self as IntoBezPathTolerance>::into_bezpath_with_tolerance(self, DEFAULT_TOLERANCE)
}
}
impl IntoBezPathTolerance for &[(f64, f64)] {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
points_to_bezpath(self.iter().copied())
}
}
impl IntoBezPathTolerance for Vec<(f64, f64)> {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
points_to_bezpath(self)
}
}
impl IntoBezPathTolerance for &[Point] {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
points_to_bezpath(self.iter().copied())
}
}
impl<const N: usize> IntoBezPathTolerance for [Point; N] {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
points_to_bezpath(self.iter().copied())
}
}
impl IntoBezPathTolerance for &Vec<Point> {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
points_to_bezpath(self.iter().copied())
}
}
impl IntoBezPathTolerance for &[(Point, Point)] {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
line_segment_to_bezpath(self.iter().copied())
}
}
impl<const N: usize> IntoBezPathTolerance for [(Point, Point); N] {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
line_segment_to_bezpath(self)
}
}
macro_rules! kurbo_shape_into_bezpath {
($t:ty) => {
impl IntoBezPathTolerance for $t {
fn into_bezpath_with_tolerance(self, tolerance: f64) -> BezPath {
<$t as kurbo::Shape>::into_path(self, tolerance)
}
}
};
}
kurbo_shape_into_bezpath!(kurbo::PathSeg);
kurbo_shape_into_bezpath!(kurbo::Arc);
kurbo_shape_into_bezpath!(kurbo::BezPath);
kurbo_shape_into_bezpath!(kurbo::Circle);
kurbo_shape_into_bezpath!(kurbo::CircleSegment);
kurbo_shape_into_bezpath!(kurbo::CubicBez);
kurbo_shape_into_bezpath!(kurbo::Ellipse);
kurbo_shape_into_bezpath!(kurbo::Line);
kurbo_shape_into_bezpath!(kurbo::QuadBez);
kurbo_shape_into_bezpath!(kurbo::Rect);
kurbo_shape_into_bezpath!(kurbo::RoundedRect);
impl IntoBezPathTolerance for Polyline {
fn into_bezpath_with_tolerance(self, tolerance: f64) -> BezPath {
self.points().into_bezpath_with_tolerance(tolerance)
}
}
#[cfg(feature = "geo")]
pub mod geo_impl {
#[allow(clippy::wildcard_imports)]
use super::*;
impl IntoBezPathTolerance for geo::Geometry<f64> {
fn into_bezpath_with_tolerance(self, _tolerance: f64) -> BezPath {
match self {
geo::Geometry::Point(pt) => BezPath::from_vec(vec![
PathEl::MoveTo((pt.x(), pt.y()).into()),
PathEl::LineTo((pt.x(), pt.y()).into()),
]),
geo::Geometry::MultiPoint(mp) => BezPath::from_vec(
mp.into_iter()
.flat_map(|pt| {
[
PathEl::MoveTo((pt.x(), pt.y()).into()),
PathEl::LineTo((pt.x(), pt.y()).into()),
]
})
.collect(),
),
geo::Geometry::Line(line) => {
BezPath::from_path_segments(std::iter::once(PathSeg::Line(kurbo::Line::new(
(line.start.x, line.start.y),
(line.end.x, line.end.y),
))))
}
geo::Geometry::LineString(pts) => linestring_to_path_el(pts).collect::<BezPath>(),
geo::Geometry::MultiLineString(mls) => mls
.into_iter()
.flat_map(linestring_to_path_el)
.collect::<BezPath>(),
geo::Geometry::Polygon(poly) => {
let (exterior, interiors) = poly.into_inner();
linestring_to_path_el(exterior)
.chain(interiors.into_iter().flat_map(linestring_to_path_el))
.collect::<BezPath>()
}
geo::Geometry::MultiPolygon(mp) => mp
.into_iter()
.flat_map(|poly| {
let (exterior, interiors) = poly.into_inner();
linestring_to_path_el(exterior)
.chain(interiors.into_iter().flat_map(linestring_to_path_el))
})
.collect::<BezPath>(),
geo::Geometry::GeometryCollection(coll) => coll
.into_iter()
.flat_map(IntoBezPath::into_bezpath)
.collect(),
geo::Geometry::Rect(rect) => BezPath::from_vec(vec![
PathEl::MoveTo((rect.min().x, rect.min().y).into()),
PathEl::LineTo((rect.min().x, rect.max().y).into()),
PathEl::LineTo((rect.max().x, rect.max().y).into()),
PathEl::LineTo((rect.max().x, rect.min().y).into()),
PathEl::ClosePath,
]),
geo::Geometry::Triangle(tri) => BezPath::from_vec(vec![
PathEl::MoveTo((tri.0.x, tri.0.y).into()),
PathEl::LineTo((tri.1.x, tri.1.y).into()),
PathEl::LineTo((tri.2.x, tri.2.y).into()),
PathEl::ClosePath,
]),
}
}
}
macro_rules! geo_object_into_bezpath {
( $ t: ty) => {
impl IntoBezPathTolerance for $t {
fn into_bezpath_with_tolerance(self, tolerance: f64) -> BezPath {
let geom: ::geo::Geometry = self.into();
geom.into_bezpath_with_tolerance(tolerance)
}
}
};
}
geo_object_into_bezpath!(geo::Point<f64>);
geo_object_into_bezpath!(geo::Line<f64>);
geo_object_into_bezpath!(geo::LineString<f64>);
geo_object_into_bezpath!(geo::Polygon<f64>);
geo_object_into_bezpath!(geo::MultiPoint<f64>);
geo_object_into_bezpath!(geo::MultiLineString<f64>);
geo_object_into_bezpath!(geo::MultiPolygon<f64>);
geo_object_into_bezpath!(geo::Rect<f64>);
geo_object_into_bezpath!(geo::Triangle<f64>);
pub(super) fn linestring_to_path_el(ls: geo::LineString<f64>) -> impl Iterator<Item = PathEl> {
let closed = ls.is_closed();
let len = ls.0.len();
ls.into_iter().enumerate().map(move |(i, pt)| {
if i == 0 {
PathEl::MoveTo(coord_to_point(pt).into())
} else if i == len - 1 && closed {
PathEl::ClosePath
} else {
PathEl::LineTo(coord_to_point(pt).into())
}
})
}
#[inline]
pub(super) fn coord_to_point(c: geo::Coord<f64>) -> Point {
(c.x, c.y).into()
}
}
pub(crate) fn points_to_bezpath(
points: impl IntoIterator<Item = impl Into<Point>>,
) -> kurbo::BezPath {
let mut bezpath = kurbo::BezPath::new();
let mut points = points.into_iter().map(Into::into);
if let Some(pt) = points.next() {
bezpath.move_to(pt);
}
for pt in points {
bezpath.line_to(pt);
}
bezpath
}
pub(crate) fn line_segment_to_bezpath(
segments: impl IntoIterator<Item = impl Into<(Point, Point)>>,
) -> BezPath {
let segments = segments
.into_iter()
.map(Into::into)
.map(|(a, b)| kurbo::PathSeg::Line(kurbo::Line::new(a, b)));
BezPath::from_path_segments(segments)
}
#[cfg(test)]
mod test {
use super::geo_impl::*;
use super::*;
#[test]
fn test_linestring_to_path_el() {
assert_eq!(
linestring_to_path_el(geo::LineString(vec![])).collect::<Vec<_>>(),
vec![]
);
assert_eq!(
linestring_to_path_el(geo::LineString(vec![(0., 0.).into()])).collect::<Vec<_>>(),
vec![PathEl::MoveTo((0., 0.).into())]
);
assert_eq!(
linestring_to_path_el(geo::LineString(vec![
(0., 0.).into(),
(1., 1.).into(),
(2., 2.).into()
]))
.collect::<Vec<_>>(),
vec![
PathEl::MoveTo((0., 0.).into()),
PathEl::LineTo((1., 1.).into()),
PathEl::LineTo((2., 2.).into()),
]
);
assert_eq!(
linestring_to_path_el(geo::LineString(vec![
(0., 0.).into(),
(1., 1.).into(),
(2., 2.).into(),
(0., 0.).into(),
]))
.collect::<Vec<_>>(),
vec![
PathEl::MoveTo((0., 0.).into()),
PathEl::LineTo((1., 1.).into()),
PathEl::LineTo((2., 2.).into()),
PathEl::ClosePath,
]
);
let mut ls = geo::LineString(vec![(0., 0.).into(), (1., 1.).into(), (2., 2.).into()]);
ls.close();
assert_eq!(
linestring_to_path_el(ls).collect::<Vec<_>>(),
vec![
PathEl::MoveTo((0., 0.).into()),
PathEl::LineTo((1., 1.).into()),
PathEl::LineTo((2., 2.).into()),
PathEl::ClosePath,
]
);
}
#[test]
fn test_points_to_bezpath() {
let points = vec![[0.0, 0.0], [10.0, 12.0], [1.0, 2.0]];
assert_eq!(
points_to_bezpath(points),
BezPath::from_vec(vec![
PathEl::MoveTo(kurbo::Point::new(0.0, 0.0)),
PathEl::LineTo(kurbo::Point::new(10.0, 12.0)),
PathEl::LineTo(kurbo::Point::new(1.0, 2.0))
])
);
}
#[test]
fn test_points_to_bezpath_empty() {
let points: [Point; 0] = [];
assert!(points_to_bezpath(points).is_empty());
let points = [Point::new(0.0, 0.0)];
assert!(points_to_bezpath(points).is_empty());
}
#[test]
fn test_line_segments_to_bezpath() {
let segs = [
(Point::new(0.0, 0.0), Point::new(10.0, 12.0)),
(Point::new(1.0, 2.0), Point::new(3.0, 4.0)),
];
let bezpath = line_segment_to_bezpath(segs);
assert_eq!(
bezpath,
BezPath::from_vec(vec![
PathEl::MoveTo(kurbo::Point::new(0.0, 0.0)),
PathEl::LineTo(kurbo::Point::new(10.0, 12.0)),
PathEl::MoveTo(kurbo::Point::new(1.0, 2.0)),
PathEl::LineTo(kurbo::Point::new(3.0, 4.0))
])
);
}
}