use std::fmt;
use crate::cxx_bridge::clipper2_sys_cxx;
use crate::paths_blob::{paths64_to_blob, PathsBlob64Iter};
use crate::poly_path::PolyCxxPreorderIter64;
use crate::{ClipType, FillRule, Paths64};
use super::LazyPaths64;
#[derive(Clone, Debug)]
pub struct ClipSolution64 {
closed: clipper2_sys_cxx::PathsBlob64,
open: clipper2_sys_cxx::PathsBlob64,
}
impl ClipSolution64 {
#[inline]
pub fn closed_is_empty(&self) -> bool {
self.closed.path_starts.len() < 2
}
#[inline]
pub fn open_is_empty(&self) -> bool {
self.open.path_starts.len() < 2
}
#[inline]
pub fn iter_closed(&self) -> PathsBlob64Iter<'_> {
PathsBlob64Iter::new(&self.closed)
}
#[inline]
pub fn iter_open(&self) -> PathsBlob64Iter<'_> {
PathsBlob64Iter::new(&self.open)
}
pub fn to_closed(&self) -> Paths64 {
self.iter_closed().collect()
}
pub fn to_open(&self) -> Paths64 {
self.iter_open().collect()
}
pub fn into_lazy(self) -> (LazyPaths64, LazyPaths64) {
(
LazyPaths64::from_blob(self.closed),
LazyPaths64::from_blob(self.open),
)
}
}
pub struct ClipTreeSolution64 {
poly_root: Option<usize>,
open: clipper2_sys_cxx::PathsBlob64,
}
impl fmt::Debug for ClipTreeSolution64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClipTreeSolution64")
.field("has_poly_root", &self.poly_root.map(|r| r != 0))
.field("open_paths", &(self.open.path_starts.len().saturating_sub(1)))
.finish()
}
}
impl Drop for ClipTreeSolution64 {
fn drop(&mut self) {
if let Some(r) = self.poly_root.take() {
if r != 0 {
clipper2_sys_cxx::cxx_poly64_delete(r);
}
}
}
}
impl ClipTreeSolution64 {
#[inline]
pub fn has_poly_tree(&self) -> bool {
matches!(self.poly_root, Some(r) if r != 0)
}
#[inline]
pub fn open_is_empty(&self) -> bool {
self.open.path_starts.len() < 2
}
#[inline]
pub fn iter_open(&self) -> PathsBlob64Iter<'_> {
PathsBlob64Iter::new(&self.open)
}
pub fn to_open(&self) -> Paths64 {
self.iter_open().collect()
}
pub fn into_open_lazy(mut self) -> LazyPaths64 {
if let Some(r) = self.poly_root.take() {
if r != 0 {
clipper2_sys_cxx::cxx_poly64_delete(r);
}
}
LazyPaths64::from_blob(std::mem::take(&mut self.open))
}
pub fn into_open_and_poly_preorder(mut self) -> (LazyPaths64, PolyCxxPreorderIter64) {
let root = self.poly_root.take().unwrap_or(0);
let open = std::mem::take(&mut self.open);
let iter = PolyCxxPreorderIter64::new(root);
(LazyPaths64::from_blob(open), iter)
}
}
pub struct Clipper64 {
inner: cxx::UniquePtr<clipper2_sys_cxx::Clipper64Box>,
}
impl Clipper64 {
pub fn new() -> Self {
Self {
inner: clipper2_sys_cxx::cxx_clipper64_new(),
}
}
pub fn set_preserve_collinear(&mut self, value: bool) {
clipper2_sys_cxx::cxx_clipper64_set_preserve_collinear(self.inner.pin_mut(), value);
}
pub fn get_preserve_collinear(&self) -> bool {
clipper2_sys_cxx::cxx_clipper64_get_preserve_collinear(&self.inner)
}
pub fn set_reverse_solution(&mut self, value: bool) {
clipper2_sys_cxx::cxx_clipper64_set_reverse_solution(self.inner.pin_mut(), value);
}
pub fn get_reverse_solution(&self) -> bool {
clipper2_sys_cxx::cxx_clipper64_get_reverse_solution(&self.inner)
}
pub fn clear(&mut self) {
clipper2_sys_cxx::cxx_clipper64_clear(self.inner.pin_mut());
}
pub fn add_open_subject(&mut self, open_subject: &Paths64) {
clipper2_sys_cxx::cxx_clipper64_add_open_subject(
self.inner.pin_mut(),
&paths64_to_blob(open_subject),
);
}
pub fn add_subject(&mut self, subject: &Paths64) {
clipper2_sys_cxx::cxx_clipper64_add_subject(self.inner.pin_mut(), &paths64_to_blob(subject));
}
pub fn add_clip(&mut self, clip: &Paths64) {
clipper2_sys_cxx::cxx_clipper64_add_clip(self.inner.pin_mut(), &paths64_to_blob(clip));
}
pub fn execute(&mut self, clip_type: ClipType, fill_rule: FillRule) -> ClipSolution64 {
let out = clipper2_sys_cxx::cxx_clipper64_execute(
self.inner.pin_mut(),
clip_type.into(),
fill_rule.into(),
);
ClipSolution64 {
closed: out.closed,
open: out.open,
}
}
pub fn execute_tree(&mut self, clip_type: ClipType, fill_rule: FillRule) -> ClipTreeSolution64 {
let te = clipper2_sys_cxx::cxx_clipper64_execute_tree(
self.inner.pin_mut(),
clip_type.into(),
fill_rule.into(),
);
ClipTreeSolution64 {
poly_root: if te.root == 0 { None } else { Some(te.root) },
open: te.open,
}
}
}
#[cfg(test)]
mod tests {
use crate::{ClipType, Clipper64, FillRule, Path64, Paths64, Point64};
fn square(x0: i64, y0: i64, s: i64) -> Path64 {
Path64::new(vec![
Point64::new(x0, y0),
Point64::new(x0 + s, y0),
Point64::new(x0 + s, y0 + s),
Point64::new(x0, y0 + s),
])
}
#[test]
fn union_overlapping_squares_non_empty() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute(ClipType::Union, FillRule::NonZero);
assert!(!sol.closed_is_empty() || !sol.open_is_empty());
}
#[test]
fn execute_iter_closed_collect_matches_into_lazy() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute(ClipType::Union, FillRule::NonZero);
let a: Paths64 = sol.iter_closed().chain(sol.iter_open()).collect();
let (closed, open) = sol.into_lazy();
let mut b = closed.into_paths();
b.0.extend(open.into_paths().0);
assert_eq!(a.len(), b.len());
}
#[test]
fn execute_tree_poly_preorder_count_stable() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute_tree(ClipType::Union, FillRule::NonZero);
let (_, iter_a) = sol.into_open_and_poly_preorder();
let n_a = iter_a.count();
let mut c2 = Clipper64::new();
c2.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c2.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol2 = c2.execute_tree(ClipType::Union, FillRule::NonZero);
let (_, iter_b) = sol2.into_open_and_poly_preorder();
let n_b = iter_b.count();
assert_eq!(n_a, n_b);
}
#[test]
fn execute_tree_union_has_poly_or_open() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute_tree(ClipType::Union, FillRule::NonZero);
assert!(sol.has_poly_tree() || !sol.open_is_empty());
}
#[test]
fn intersection_overlapping_squares_non_empty() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute(ClipType::Intersection, FillRule::NonZero);
assert!(!sol.closed_is_empty() || !sol.open_is_empty());
}
#[test]
fn difference_frame_non_empty() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(25, 25, 50)]));
let sol = c.execute(ClipType::Difference, FillRule::NonZero);
assert!(!sol.closed_is_empty() || !sol.open_is_empty());
}
#[test]
fn xor_overlapping_squares_non_empty() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute(ClipType::Xor, FillRule::NonZero);
assert!(!sol.closed_is_empty() || !sol.open_is_empty());
}
#[test]
fn union_even_odd_fill_non_empty() {
let mut c = Clipper64::new();
c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
let sol = c.execute(ClipType::Union, FillRule::EvenOdd);
assert!(!sol.closed_is_empty() || !sol.open_is_empty());
}
#[test]
fn open_subject_line_intersection_with_clip_non_empty() {
let mut c = Clipper64::new();
let line = Path64::new(vec![
Point64::new(-10, 50),
Point64::new(110, 50),
]);
c.add_open_subject(&Paths64::new(vec![line]));
c.add_clip(&Paths64::new(vec![square(0, 0, 100)]));
let sol = c.execute(ClipType::Intersection, FillRule::NonZero);
assert!(!sol.open_is_empty() || !sol.closed_is_empty());
}
}