1#[cfg(feature = "optimise")]
20#[cfg(feature = "parse")]
21#[macro_use]
22extern crate bitflags;
23
24#[cfg(feature = "napi")]
25#[macro_use]
26extern crate napi_derive;
27
28#[cfg(feature = "optimise")]
29pub mod command;
30#[cfg(feature = "optimise")]
31pub mod convert;
32#[cfg(feature = "optimise")]
33pub mod geometry;
34#[cfg(feature = "optimise")]
35pub(crate) mod math;
36#[cfg(feature = "parse")]
37pub mod parser;
38#[cfg(feature = "optimise")]
39pub mod points;
40#[cfg(feature = "optimise")]
41pub mod positioned;
42
43use points::{Point, Points};
44
45#[derive(Debug, Clone, Default, PartialEq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
48pub struct Path(pub Vec<command::Data>);
63
64impl Path {
65 pub fn intersects(&self, other: &Self) -> bool {
71 let points_1 = Points::from_positioned(&convert::relative(self.clone()));
72 let points_2 = Points::from_positioned(&convert::relative(other.clone()));
73
74 if points_1.max_x <= points_2.min_x
76 || points_2.max_x <= points_1.min_x
77 || points_1.max_y <= points_2.min_y
78 || points_2.max_y <= points_1.min_y
79 || points_1.list.iter().all(|set_1| {
80 points_2.list.iter().all(|set_2| {
81 set_1.list[set_1.max_x].0[0] <= set_2.list[set_2.min_x].0[0]
82 || set_2.list[set_2.max_x].0[0] <= set_1.list[set_1.min_x].0[0]
83 || set_1.list[set_1.max_y].0[1] <= set_2.list[set_2.min_y].0[1]
84 || set_2.list[set_2.max_y].0[1] <= set_1.list[set_1.min_y].0[1]
85 })
86 })
87 {
88 log::debug!("no intersection, bounds check failed");
89 return false;
90 }
91
92 let mut hull_nest_1 = points_1.list.into_iter().map(Point::convex_hull);
94 let hull_nest_2: Vec<_> = points_2.list.into_iter().map(Point::convex_hull).collect();
95
96 hull_nest_1.any(|hull_1| {
97 if hull_1.list.len() < 3 {
98 return false;
99 }
100
101 hull_nest_2.iter().any(|hull_2| {
102 if hull_2.list.len() < 3 {
103 return false;
104 }
105
106 let mut simplex = vec![hull_1.get_support(hull_2, geometry::Point([1.0, 0.0]))];
107 let mut direction = simplex[0].minus();
108 let mut iterations = 10_000;
109
110 loop {
111 iterations -= 1;
112 if iterations == 0 {
113 log::error!("Infinite loop while finding path intersections");
114 return true;
115 }
116 simplex.push(hull_1.get_support(hull_2, direction));
117 if direction.dot(simplex.last().unwrap()) <= 0.0 {
118 return false;
119 }
120 if geometry::Point::process_simplex(&mut simplex, &mut direction) {
121 return true;
122 }
123 }
124 })
125 })
126 }
127}
128
129#[cfg(feature = "format")]
130pub(crate) fn format<'a>(
131 mut iter: impl ExactSizeIterator<Item = &'a command::Data>,
132 f: &mut std::fmt::Formatter<'_>,
133) -> std::fmt::Result {
134 use itertools::Itertools;
135 use std::fmt::Display;
136 use std::fmt::Write;
137
138 if iter.len() == 1 {
139 iter.next().unwrap().fmt(f)?;
140 return Ok(());
141 }
142 iter.tuple_windows()
143 .enumerate()
144 .try_for_each(|(i, (prev, current))| -> std::fmt::Result {
145 if i == 0 {
146 prev.fmt(f)?;
147 }
148 let str = current.to_string();
149 if current.is_space_needed(prev) && !str.starts_with('-') {
150 f.write_char(' ')?;
151 }
152 f.write_str(&str)?;
153 Ok(())
154 })
155}
156#[cfg(feature = "format")]
157impl std::fmt::Display for Path {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 format(self.0.iter(), f)
160 }
161}
162#[cfg(feature = "format")]
163impl std::fmt::Display for positioned::Path {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 format(self.0.iter().map(|p| &p.command), f)
166 }
167}
168
169#[cfg(feature = "format")]
170impl From<Path> for String {
171 fn from(value: Path) -> Self {
172 format!("{value}")
173 }
174}
175
176#[cfg(feature = "format")]
177impl From<&Path> for String {
178 fn from(value: &Path) -> Self {
179 format!("{value}")
180 }
181}
182
183#[test]
184#[cfg(feature = "default")]
185fn test_path_parse() {
186 use oxvg_parse::Parse as _;
187 insta::assert_snapshot!(Path::parse_string("M 10,50").unwrap());
189
190 insta::assert_snapshot!(
192 Path::parse_string("M 10,50 C 20,30 40,50 60,70 C 10,20 30,40 50,60").unwrap()
193 );
194
195 insta::assert_snapshot!(Path::parse_string("m-0,1a 25,25 -30 0,1 0,0").unwrap());
197
198 insta::assert_snapshot!(Path::parse_string(
200 "M 10,50 C 1,2 3,4 5,6.5 .1 .2 .3 .4 .5 -.05176e-005"
201 )
202 .unwrap());
203
204 insta::assert_snapshot!(Path::parse_string("M10 50C1 2 3 4 5 6.5.1.2.3.4.5-5.176e-7").unwrap());
206
207 assert!(Path::parse_string("0,0").is_err());
209
210 assert!(Path::parse_string("m1").is_err());
212
213 insta::assert_snapshot!(Path::parse_string("m-0,1a20.8 20.8 0 0 0 5.2.6").unwrap());
215
216 insta::assert_snapshot!(Path::parse_string(
218 "m-0,1a29.6 29.6 0 01-2 1.5 151.6 151.6 0 01-2.6 1.8"
219 )
220 .unwrap());
221}