1use geo::{Coordinate, MultiPolygon};
2use std::fmt;
3
4#[derive(Debug, Clone)]
6pub struct Column<U = super::Unit> {
7 array: Vec<U>,
8 align: crate::Align,
9 bbox: bool,
10}
11
12impl<U: super::Feature + fmt::Debug + Clone> Column<U> {
13 pub fn align_left(array: Vec<U>) -> Self {
16 Self::new(array, crate::Align::Start)
17 }
18
19 pub fn align_right(array: Vec<U>) -> Self {
22 Self::new(array, crate::Align::End)
23 }
24
25 pub fn align_center(array: Vec<U>) -> Self {
28 Self::new(array, crate::Align::Center)
29 }
30
31 fn new(mut array: Vec<U>, align: crate::Align) -> Self {
32 for e in array.iter_mut() {
35 if let Some(b) = e.edge_union() {
36 use geo::bounding_rect::BoundingRect;
37 let v = b.bounding_rect().unwrap().min();
38 e.translate(-v);
39 }
40 }
41
42 Self {
43 align,
44 array,
45 bbox: true,
46 }
47 }
48
49 fn all_bounds(&self) -> Vec<geo::Rect<f64>> {
50 self.array
51 .iter()
52 .map(|f| {
53 let add_b = match f.edge_union() {
54 Some(edge) => {
55 use geo::bounding_rect::BoundingRect;
56 edge.bounding_rect()
57 }
58 None => None,
59 };
60 let sub_b = match f.edge_subtract() {
61 Some(edge) => {
62 use geo::bounding_rect::BoundingRect;
63 edge.bounding_rect()
64 }
65 None => None,
66 };
67
68 match (add_b, sub_b) {
69 (Some(b), None) => b,
70 (None, Some(b)) => b,
71 (Some(u), Some(s)) => {
72 use geo::bounding_rect::BoundingRect;
73 use geo_booleanop::boolean::BooleanOp;
74 u.to_polygon()
75 .union(&s.to_polygon())
76 .bounding_rect()
77 .unwrap()
78 }
79 (None, None) => geo::Rect::new(
80 Coordinate::<f64> { x: 0., y: 0. },
81 Coordinate::<f64> { x: 0., y: 0. },
82 ),
83 }
84 })
85 .collect()
86 }
87
88 fn largest(&self) -> geo::Rect<f64> {
89 self.all_bounds()
90 .into_iter()
91 .max_by(|x, y| x.width().partial_cmp(&y.width()).unwrap())
92 .unwrap()
93 }
94
95 fn translations<'a>(
96 &'a self,
97 largest: geo::Rect<f64>,
98 ) -> Box<dyn Iterator<Item = Option<(f64, f64)>> + 'a> {
99 Box::new(
100 self.all_bounds()
101 .iter()
102 .scan(0f64, |y_off, b| {
103 let out = Some((b, *y_off));
104 *y_off = *y_off + b.height();
105 out
106 })
107 .map(move |(bounds, y_off)| {
108 Some(match self.align {
109 crate::Align::Start => (largest.min().x - bounds.min().x, y_off),
110 crate::Align::End => (largest.max().x - bounds.max().x, y_off),
111 crate::Align::Center => (largest.center().x - bounds.center().x, y_off),
112 })
113 })
114 .collect::<Vec<_>>()
115 .into_iter(),
116 )
117 }
118}
119
120impl<U: super::Feature + fmt::Debug> fmt::Display for Column<U> {
121 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122 write!(f, "Column(align = {:?}, {:?})", self.align, self.array)
123 }
124}
125
126impl<U: super::Feature + fmt::Debug + Clone> super::Feature for Column<U> {
127 fn name(&self) -> &'static str {
128 "Column"
129 }
130
131 fn edge_union(&self) -> Option<MultiPolygon<f64>> {
132 let out = self
133 .array
134 .iter()
135 .map(|f| match f.edge_union() {
136 Some(edge_geo) => Some(edge_geo.clone()),
137 None => None,
138 })
139 .zip(self.translations(self.largest()).into_iter())
140 .filter(|(f, t)| f.is_some() && t.is_some())
141 .map(|(f, t)| (f.unwrap(), t.unwrap()))
142 .fold(None, |mut acc, (g, (tx, ty))| {
143 use geo::translate::Translate;
144 use geo_booleanop::boolean::BooleanOp;
145 if let Some(current) = acc {
146 acc = Some(g.translate(tx, ty).union(¤t));
147 } else {
148 acc = Some(g.translate(tx, ty));
149 };
150 acc
151 });
152
153 if self.bbox {
156 match out {
157 None => None,
158 Some(poly) => {
159 use geo::bounding_rect::BoundingRect;
160 Some(poly.bounding_rect().unwrap().to_polygon().into())
161 }
162 }
163 } else {
164 out
165 }
166 }
167
168 fn edge_subtract(&self) -> Option<MultiPolygon<f64>> {
169 let out = self
170 .array
171 .iter()
172 .map(|f| match f.edge_subtract() {
173 Some(edge_geo) => Some(edge_geo.clone()),
174 None => None,
175 })
176 .zip(self.translations(self.largest()).into_iter())
177 .filter(|(f, t)| f.is_some() && t.is_some())
178 .map(|(f, t)| (f.unwrap(), t.unwrap()))
179 .fold(None, |mut acc, (g, (tx, ty))| {
180 use geo::translate::Translate;
181 use geo_booleanop::boolean::BooleanOp;
182 if let Some(current) = acc {
183 acc = Some(g.translate(tx, ty).union(¤t));
184 } else {
185 acc = Some(g.translate(tx, ty));
186 };
187 acc
188 });
189
190 out
191 }
192
193 fn translate(&mut self, v: Coordinate<f64>) {
194 for e in self.array.iter_mut() {
195 e.translate(v);
196 }
197 }
198
199 fn interior(&self) -> Vec<super::InnerAtom> {
200 let largest = self.largest();
201
202 self.array
203 .iter()
204 .map(|f| f.interior())
205 .zip(self.translations(largest).into_iter())
206 .map(|(f, t)| {
207 let (tx, ty) = match t {
208 Some((tx, ty)) => (tx, ty),
209 None => (0., 0.),
210 };
211
212 f.into_iter().map(move |mut a| {
213 a.translate(tx, ty);
214 a
215 })
216 })
217 .flatten()
218 .collect()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::features::Rect;
226 use test_case::test_case;
227
228 #[test]
229 fn bounds() {
230 let a = Column::align_left(vec![
231 Rect::with_center([0., 0.].into(), 2., 3.),
232 Rect::with_center([0., 0.].into(), 3., 2.),
233 ]);
234
235 assert_eq!(
236 a.all_bounds(),
237 vec![
238 geo::Rect::new::<geo::Coordinate<_>>([0., 0.].into(), [2., 3.].into()),
239 geo::Rect::new::<geo::Coordinate<_>>([0., 0.].into(), [3., 2.].into()),
240 ]
241 );
242 }
243
244 #[test]
245 fn largest() {
246 let a = Column::align_left(vec![
247 Rect::with_center([0., 0.].into(), 2., 3.),
248 Rect::with_center([0., 0.].into(), 3., 2.),
249 ]);
250
251 assert_eq!(
252 a.largest(),
253 geo::Rect::new::<geo::Coordinate<_>>([0., 0.].into(), [3., 2.].into(),),
254 );
255 }
256
257 #[test_case(
258 vec![
259 Rect::with_center([0., 0.].into(), 4., 2.),
260 Rect::with_center([0., 0.].into(), 2., 2.),
261 ], vec![
262 Some((0., 0.)),
263 Some((0., 2.)),
264 ] ; "origin centered"
265 )
266 ]
267 fn translations_left(inners: Vec<Rect>, want: Vec<Option<(f64, f64)>>) {
268 let a = Column::align_left(inners);
269 assert_eq!(a.translations(a.largest()).collect::<Vec<_>>(), want,);
270 }
271
272 #[test_case( vec![
273 Rect::with_center([0., 0.].into(), 2., 2.),
274 Rect::with_center([0., 0.].into(), 4., 2.),
275 ], vec![
276 Some((1., 0.)),
277 Some((0., 2.)),
278 ] ; "origin centered")]
279 #[test_case( vec![
280 Rect::new([0., 0.].into(), [2., 3.].into()),
281 Rect::new([0., 0.].into(), [2., 2.].into()),
282 ], vec![
283 Some((0., 0.)),
284 Some((0., 3.)),
285 ] ; "origin tl zeroed")]
286 fn translations_center(inners: Vec<Rect>, want: Vec<Option<(f64, f64)>>) {
287 let a = Column::align_center(inners);
288 assert_eq!(a.translations(a.largest()).collect::<Vec<_>>(), want,);
289 }
290}