Skip to main content

klayout_core/
shape.rs

1//! Geometry primitives, as data only.
2//!
3//! No boolean ops, no sizing, no point-in-polygon. Algorithms live in the
4//! `klayout-geom` crate so the geometry backend can be swapped without
5//! touching the data model.
6
7use crate::coord::{Bbox, Point, Trans};
8use smallvec::SmallVec;
9use smol_str::SmolStr;
10
11/// Sort holes by their first vertex (which after normalization is the
12/// lowest-y/lowest-x point of each ring). Stable for identical first
13/// vertices using vertex count as the tiebreaker.
14fn 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
22/// Canonicalize a polygon ring in place: lowest-y/lowest-x first.
23/// `want_ccw = false` (default for hulls) normalizes to CW;
24/// `want_ccw = true` (for holes) normalizes to CCW.
25fn 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    /// Build a polygon from a hull. The hull is canonicalized:
70    /// clockwise winding, starting from the lowest-y/lowest-x vertex.
71    /// This matches KLayout's canonical form so cross-tool round-trips
72    /// preserve byte-equal vertex sequences.
73    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    /// Like `from_hull` but skips canonicalization. Use when you specifically
83    /// need to preserve a particular vertex order (extremely rare; mainly for
84    /// tests that probe normalization itself).
85    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    /// Canonicalize in place: hull → lowest-y/lowest-x first, CW winding;
93    /// holes → lowest-y/lowest-x first, CCW winding; hole list sorted by
94    /// the first vertex of each hole so polygons that differ only in
95    /// hole-construction order hash identically.
96    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    /// Append a hole, normalizing winding and re-sorting the hole list.
105    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}