1use crate::{Align, Direction};
2use geo::{Coordinate, MultiPolygon};
3use std::fmt;
4
5#[derive(Debug, Clone)]
7pub enum Positioning {
8 Cardinal {
9 side: Direction,
10 centerline_adjustment: f64,
11 align: Align,
12 },
13 Corner {
14 side: Direction,
15 opposite: bool,
16 align: Align,
17 },
18 Angle {
19 degrees: f64,
20 amount: f64,
21 },
22}
23
24impl Positioning {
25 fn compute_translation(&self, bounds: geo::Rect<f64>, feature: geo::Rect<f64>) -> (f64, f64) {
26 match self {
27 Positioning::Cardinal {
28 side,
29 centerline_adjustment,
30 align: _,
31 } => match side {
32 Direction::Left => (
33 bounds.min().x - self.compute_align_ref(feature),
34 bounds.center().y - feature.center().y
35 + (centerline_adjustment * bounds.height()),
36 ),
37 Direction::Right => (
38 bounds.max().x - self.compute_align_ref(feature),
39 bounds.center().y - feature.center().y
40 + (centerline_adjustment * bounds.height()),
41 ),
42 Direction::Up => (
43 bounds.center().x - feature.center().x
44 + (centerline_adjustment * bounds.width()),
45 bounds.min().y - self.compute_align_ref(feature),
46 ),
47 Direction::Down => (
48 bounds.center().x - feature.center().x
49 + (centerline_adjustment * bounds.width()),
50 bounds.max().y - self.compute_align_ref(feature),
51 ),
52 },
53 Positioning::Corner {
54 side,
55 opposite,
56 align: _,
57 } => match side {
58 Direction::Left => (
59 bounds.min().x - self.compute_align_ref(feature),
60 match opposite {
61 false => bounds.min().y - feature.min().y,
62 true => bounds.max().y - feature.max().y,
63 },
64 ),
65 Direction::Right => (
66 bounds.max().x - self.compute_align_ref(feature),
67 match opposite {
68 false => bounds.min().y - feature.min().y,
69 true => bounds.max().y - feature.max().y,
70 },
71 ),
72 Direction::Up => (
73 match opposite {
74 false => bounds.min().x - feature.min().x,
75 true => bounds.max().x - feature.max().x,
76 },
77 bounds.min().y - self.compute_align_ref(feature),
78 ),
79 Direction::Down => (
80 match opposite {
81 false => bounds.min().x - feature.min().x,
82 true => bounds.max().x - feature.max().x,
83 },
84 bounds.max().y - self.compute_align_ref(feature),
85 ),
86 },
87 Positioning::Angle { degrees, amount } => {
88 let r = degrees * std::f64::consts::PI / 180.;
89 (
90 bounds.center().x + (amount * r.cos()),
91 bounds.center().y + (amount * r.sin()),
92 )
93 }
94 }
95 }
96
97 fn compute_align_ref(&self, feature: geo::Rect<f64>) -> f64 {
98 match self {
99 Positioning::Cardinal {
100 side,
101 align,
102 centerline_adjustment: _,
103 } => match side {
104 Direction::Left => match align {
105 Align::Start => feature.min().x,
106 Align::Center => feature.center().x,
107 Align::End => feature.max().x,
108 },
109 Direction::Right => match align {
110 Align::Start => feature.max().x,
111 Align::Center => feature.center().x,
112 Align::End => feature.min().x,
113 },
114 Direction::Up => match align {
115 Align::Start => feature.min().y,
116 Align::Center => feature.center().y,
117 Align::End => feature.max().y,
118 },
119 Direction::Down => match align {
120 Align::Start => feature.max().y,
121 Align::Center => feature.center().y,
122 Align::End => feature.min().y,
123 },
124 },
125 Positioning::Corner {
126 side,
127 align,
128 opposite: _,
129 } => match side {
130 Direction::Left => match align {
131 Align::Start => feature.min().x,
132 Align::Center => feature.center().x,
133 Align::End => feature.max().x,
134 },
135 Direction::Right => match align {
136 Align::Start => feature.max().x,
137 Align::Center => feature.center().x,
138 Align::End => feature.min().x,
139 },
140 Direction::Up => match align {
141 Align::Start => feature.min().y,
142 Align::Center => feature.center().y,
143 Align::End => feature.max().y,
144 },
145 Direction::Down => match align {
146 Align::Start => feature.max().y,
147 Align::Center => feature.center().y,
148 Align::End => feature.min().y,
149 },
150 },
151 Positioning::Angle { .. } => unreachable!(),
152 }
153 }
154}
155
156#[derive(Debug, Clone)]
158pub struct AtPos<U = super::Unit, S = super::Unit> {
159 inner: U,
160 elements: Vec<(S, Positioning)>,
161}
162
163impl<U, S> AtPos<U, S>
164where
165 U: super::Feature + std::fmt::Debug + Clone,
166 S: super::Feature + std::fmt::Debug + Clone,
167{
168 pub fn x_ends(primary: U, left: Option<S>, right: Option<S>) -> Self {
171 let mut elements = Vec::with_capacity(2);
172 if let Some(left) = left {
173 elements.push((
174 left,
175 Positioning::Cardinal {
176 side: Direction::Left,
177 centerline_adjustment: 0.0,
178 align: Align::Center,
179 },
180 ));
181 }
182 if let Some(right) = right {
183 elements.push((
184 right,
185 Positioning::Cardinal {
186 side: Direction::Right,
187 centerline_adjustment: 0.0,
188 align: Align::Center,
189 },
190 ));
191 }
192 Self {
193 elements,
194 inner: primary,
195 }
196 }
197
198 pub fn new(primary: U) -> Self {
200 let elements = Vec::with_capacity(4);
201 Self {
202 elements,
203 inner: primary,
204 }
205 }
206
207 pub fn left(primary: U, left: S) -> Self {
210 let mut elements = Vec::with_capacity(2);
211 elements.push((
212 left,
213 Positioning::Cardinal {
214 side: Direction::Left,
215 centerline_adjustment: 0.0,
216 align: Align::Center,
217 },
218 ));
219
220 Self {
221 elements,
222 inner: primary,
223 }
224 }
225
226 pub fn right(primary: U, right: S) -> Self {
229 let mut elements = Vec::with_capacity(2);
230 elements.push((
231 right,
232 Positioning::Cardinal {
233 side: Direction::Right,
234 centerline_adjustment: 0.0,
235 align: Align::Center,
236 },
237 ));
238
239 Self {
240 elements,
241 inner: primary,
242 }
243 }
244
245 pub fn push(&mut self, feature: S, pos: Positioning) {
248 self.elements.push((feature, pos));
249 }
250
251 fn feature_bounds(&self, feature: &S) -> Option<geo::Rect<f64>> {
252 let union_bounds = if let Some(geo) = feature.edge_union() {
253 Some(compute_bounds(geo.clone()))
254 } else {
255 None
256 };
257
258 let subtract_bounds = if let Some(geo) = feature.edge_subtract() {
259 Some(compute_bounds(geo.clone()))
260 } else {
261 None
262 };
263
264 match (union_bounds, subtract_bounds) {
265 (Some(b), None) => Some(b),
266 (None, Some(b)) => Some(b),
267 (Some(u), Some(s)) => {
268 use geo::bounding_rect::BoundingRect;
269 use geo_booleanop::boolean::BooleanOp;
270 Some(
271 u.to_polygon()
272 .union(&s.to_polygon())
273 .bounding_rect()
274 .unwrap(),
275 )
276 }
277 (None, None) => None,
278 }
279 }
280}
281
282fn compute_bounds(poly: MultiPolygon<f64>) -> geo::Rect<f64> {
283 use geo::bounding_rect::BoundingRect;
284 poly.bounding_rect().unwrap()
285}
286
287impl<U, S> fmt::Display for AtPos<U, S>
288where
289 U: super::Feature + std::fmt::Debug + Clone,
290 S: super::Feature + std::fmt::Debug + Clone,
291{
292 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293 write!(f, "pos(U = {}, S = {:?})", self.inner, self.elements,)
294 }
295}
296
297impl<U, S> super::Feature for AtPos<U, S>
298where
299 U: super::Feature + std::fmt::Debug + Clone,
300 S: super::Feature + std::fmt::Debug + Clone,
301{
302 fn name(&self) -> &'static str {
303 "pos"
304 }
305
306 fn edge_union(&self) -> Option<MultiPolygon<f64>> {
307 use geo::algorithm::translate::Translate;
308 use geo_booleanop::boolean::BooleanOp;
309
310 let mut out = match self.inner.edge_union() {
311 Some(p) => p,
312 None => MultiPolygon(vec![]),
313 };
314 let bounds = compute_bounds(out.clone());
315
316 for (feature, position) in &self.elements {
317 if let Some(mut geo) = feature.edge_union() {
318 let t = position.compute_translation(bounds, self.feature_bounds(feature).unwrap());
319 geo.translate_inplace(t.0, t.1);
320 out = out.union(&geo)
321 }
322 }
323
324 if out.0.len() > 0 {
325 Some(out)
326 } else {
327 None
328 }
329 }
330
331 fn edge_subtract(&self) -> Option<MultiPolygon<f64>> {
332 let bounds = compute_bounds(match self.inner.edge_union() {
333 Some(p) => p,
334 None => MultiPolygon(vec![]),
335 });
336
337 let mut out = match self.inner.edge_subtract() {
338 Some(p) => p,
339 None => MultiPolygon(vec![]),
340 };
341
342 for (feature, position) in &self.elements {
343 if let Some(mut geo) = feature.edge_subtract() {
344 use geo::algorithm::translate::Translate;
345 use geo_booleanop::boolean::BooleanOp;
346 let t = position.compute_translation(bounds, self.feature_bounds(feature).unwrap());
347 geo.translate_inplace(t.0, t.1);
348 out = out.union(&geo)
349 }
350 }
351
352 if out.0.len() > 0 {
353 Some(out)
354 } else {
355 None
356 }
357 }
358
359 fn translate(&mut self, v: Coordinate<f64>) {
360 self.inner.translate(v);
361 }
363
364 fn interior(&self) -> Vec<super::InnerAtom> {
365 let bounds = compute_bounds(match self.inner.edge_union() {
366 Some(p) => p,
367 None => MultiPolygon(vec![]),
368 });
369
370 self.inner
371 .interior()
372 .into_iter()
373 .chain(
374 self.elements
375 .iter()
376 .map(|(feature, position)| {
377 if let Some(feature_bounds) = self.feature_bounds(feature) {
378 let t = position.compute_translation(bounds, feature_bounds);
379 let mut out = feature.interior();
380 for a in out.iter_mut() {
381 a.translate(t.0, t.1);
382 }
383 out
384 } else {
385 vec![]
386 }
387 })
388 .flatten(),
389 )
390 .collect()
391 }
392}