1use crate::coord::{Bbox, Point, Trans};
8use smallvec::SmallVec;
9use smol_str::SmolStr;
10
11fn sort_holes(holes: &mut [SmallVec<[Point; 8]>]) {
15 holes.sort_by(|a, b| {
16 let ka = a.first().copied().unwrap_or(Point::ZERO);
17 let kb = b.first().copied().unwrap_or(Point::ZERO);
18 (ka.x, ka.y, a.len()).cmp(&(kb.x, kb.y, b.len()))
19 });
20}
21
22fn normalize_ring(pts: &mut SmallVec<[Point; 8]>, want_ccw: bool) {
26 if pts.len() < 3 {
27 return;
28 }
29 let mut start = 0usize;
30 for i in 1..pts.len() {
31 let a = pts[i];
32 let s = pts[start];
33 if a.y < s.y || (a.y == s.y && a.x < s.x) {
34 start = i;
35 }
36 }
37 if start != 0 {
38 pts.rotate_left(start);
39 }
40 let mut area2: i128 = 0;
41 let n = pts.len();
42 for i in 0..n {
43 let a = pts[i];
44 let b = pts[(i + 1) % n];
45 area2 += (a.x as i128) * (b.y as i128) - (b.x as i128) * (a.y as i128);
46 }
47 let is_ccw = area2 > 0;
48 if is_ccw != want_ccw {
49 pts[1..].reverse();
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Hash)]
54pub struct Polygon {
55 pub hull: SmallVec<[Point; 8]>,
56 pub holes: Vec<SmallVec<[Point; 8]>>,
57}
58
59impl Polygon {
60 pub fn rect(b: Bbox) -> Self {
61 let mut p = Self {
62 hull: b.corners().into_iter().collect(),
63 holes: Vec::new(),
64 };
65 p.normalize();
66 p
67 }
68
69 pub fn from_hull(hull: impl IntoIterator<Item = Point>) -> Self {
74 let mut p = Self {
75 hull: hull.into_iter().collect(),
76 holes: Vec::new(),
77 };
78 p.normalize();
79 p
80 }
81
82 pub fn from_hull_raw(hull: impl IntoIterator<Item = Point>) -> Self {
86 Self {
87 hull: hull.into_iter().collect(),
88 holes: Vec::new(),
89 }
90 }
91
92 pub fn normalize(&mut self) {
97 normalize_ring(&mut self.hull, false);
98 for hole in &mut self.holes {
99 normalize_ring(hole, true);
100 }
101 sort_holes(&mut self.holes);
102 }
103
104 pub fn add_hole(&mut self, hole: impl IntoIterator<Item = Point>) {
106 let mut pts: SmallVec<[Point; 8]> = hole.into_iter().collect();
107 normalize_ring(&mut pts, true);
108 self.holes.push(pts);
109 sort_holes(&mut self.holes);
110 }
111
112 pub fn bbox(&self) -> Bbox {
113 let mut b = Bbox::EMPTY;
114 for p in &self.hull {
115 b.expand_to(*p);
116 }
117 b
118 }
119
120 pub fn transform(&self, t: Trans) -> Polygon {
121 Polygon {
122 hull: self.hull.iter().map(|p| t.apply(*p)).collect(),
123 holes: self
124 .holes
125 .iter()
126 .map(|h| h.iter().map(|p| t.apply(*p)).collect())
127 .collect(),
128 }
129 }
130}
131
132#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
133pub enum PathCap {
134 Flat,
135 Round,
136 Extended,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq, Hash)]
140pub struct Path {
141 pub points: SmallVec<[Point; 4]>,
142 pub width: i64,
143 pub begin_ext: i64,
144 pub end_ext: i64,
145 pub cap: PathCap,
146}
147
148impl Path {
149 pub fn new(points: impl IntoIterator<Item = Point>, width: i64) -> Self {
150 Self {
151 points: points.into_iter().collect(),
152 width,
153 begin_ext: 0,
154 end_ext: 0,
155 cap: PathCap::Flat,
156 }
157 }
158
159 pub fn bbox(&self) -> Bbox {
160 let mut b = Bbox::EMPTY;
161 let half = self.width / 2;
162 for p in &self.points {
163 b.expand_to(Point::new(p.x - half, p.y - half));
164 b.expand_to(Point::new(p.x + half, p.y + half));
165 }
166 b
167 }
168
169 pub fn transform(&self, t: Trans) -> Path {
170 Path {
171 points: self.points.iter().map(|p| t.apply(*p)).collect(),
172 width: self.width,
173 begin_ext: self.begin_ext,
174 end_ext: self.end_ext,
175 cap: self.cap,
176 }
177 }
178}
179
180#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
181pub struct Rect {
182 pub bbox: Bbox,
183}
184
185impl Rect {
186 pub fn new(b: Bbox) -> Self {
187 Self { bbox: b }
188 }
189 pub fn transform(self, t: Trans) -> Rect {
190 Rect {
191 bbox: t.apply_bbox(self.bbox),
192 }
193 }
194}
195
196#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
197pub enum HAlign {
198 Left,
199 Center,
200 Right,
201}
202
203#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
204pub enum VAlign {
205 Bottom,
206 Middle,
207 Top,
208}
209
210#[derive(Clone, Debug, PartialEq, Eq, Hash)]
211pub struct Text {
212 pub string: SmolStr,
213 pub anchor: Point,
214 pub size: i32,
215 pub halign: HAlign,
216 pub valign: VAlign,
217}
218
219impl Text {
220 pub fn new(s: impl Into<SmolStr>, anchor: Point) -> Self {
221 Self {
222 string: s.into(),
223 anchor,
224 size: 0,
225 halign: HAlign::Left,
226 valign: VAlign::Bottom,
227 }
228 }
229
230 pub fn bbox(&self) -> Bbox {
231 Bbox::from_point(self.anchor)
232 }
233
234 pub fn transform(&self, t: Trans) -> Text {
235 Text {
236 string: self.string.clone(),
237 anchor: t.apply(self.anchor),
238 size: self.size,
239 halign: self.halign,
240 valign: self.valign,
241 }
242 }
243}
244
245#[derive(Clone, Debug, PartialEq, Eq, Hash)]
246pub enum Shape {
247 Polygon(Polygon),
248 Path(Path),
249 Box(Rect),
250 Text(Text),
251}
252
253impl Shape {
254 pub fn bbox(&self) -> Bbox {
255 match self {
256 Shape::Polygon(p) => p.bbox(),
257 Shape::Path(p) => p.bbox(),
258 Shape::Box(r) => r.bbox,
259 Shape::Text(t) => t.bbox(),
260 }
261 }
262
263 pub fn transform(&self, t: Trans) -> Shape {
264 match self {
265 Shape::Polygon(p) => Shape::Polygon(p.transform(t)),
266 Shape::Path(p) => Shape::Path(p.transform(t)),
267 Shape::Box(r) => Shape::Box(r.transform(t)),
268 Shape::Text(x) => Shape::Text(x.transform(t)),
269 }
270 }
271
272 pub(crate) fn discriminant(&self) -> u8 {
273 match self {
274 Shape::Polygon(_) => 0,
275 Shape::Path(_) => 1,
276 Shape::Box(_) => 2,
277 Shape::Text(_) => 3,
278 }
279 }
280}
281
282impl From<Polygon> for Shape {
283 fn from(p: Polygon) -> Self {
284 Shape::Polygon(p)
285 }
286}
287impl From<Path> for Shape {
288 fn from(p: Path) -> Self {
289 Shape::Path(p)
290 }
291}
292impl From<Rect> for Shape {
293 fn from(r: Rect) -> Self {
294 Shape::Box(r)
295 }
296}
297impl From<Bbox> for Shape {
298 fn from(b: Bbox) -> Self {
299 Shape::Box(Rect::new(b))
300 }
301}
302impl From<Text> for Shape {
303 fn from(t: Text) -> Self {
304 Shape::Text(t)
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn polygon_rect_bbox() {
314 let b = Bbox::new(Point::new(0, 0), Point::new(10, 5));
315 let p = Polygon::rect(b);
316 assert_eq!(p.bbox(), b);
317 }
318
319 #[test]
320 fn shape_from_bbox() {
321 let s: Shape = Bbox::new(Point::new(0, 0), Point::new(1, 1)).into();
322 assert!(matches!(s, Shape::Box(_)));
323 }
324}