1use crate::path::Path;
6use crate::point::PointF;
7use crate::rect::{IRect, Rect};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ClipOp {
12 Intersect,
14 Difference,
16}
17
18#[derive(Debug, Clone)]
20pub enum ClipRegion {
21 Rect(IRect),
23 Path(Path),
25 Empty,
27}
28
29impl ClipRegion {
30 pub fn from_rect(rect: IRect) -> Self {
32 ClipRegion::Rect(rect)
33 }
34
35 pub fn from_path(path: Path) -> Self {
37 ClipRegion::Path(path)
38 }
39
40 pub fn empty() -> Self {
42 ClipRegion::Empty
43 }
44
45 pub fn contains_point(&self, x: i32, y: i32) -> bool {
47 match self {
48 ClipRegion::Rect(rect) => {
49 x >= rect.x
50 && x < rect.x + rect.width as i32
51 && y >= rect.y
52 && y < rect.y + rect.height as i32
53 }
54 ClipRegion::Path(path) => {
55 self.point_in_path(PointF::new(x as f32, y as f32), path)
57 }
58 ClipRegion::Empty => false,
59 }
60 }
61
62 fn point_in_path(&self, point: PointF, path: &Path) -> bool {
64 let commands = path.commands();
65 if commands.is_empty() {
66 return false;
67 }
68
69 let mut crossings = 0;
70 let mut current = PointF::new(0.0, 0.0);
71 let mut start = PointF::new(0.0, 0.0);
72
73 for cmd in commands {
74 match cmd {
75 crate::path::PathCommand::MoveTo(p) => {
76 current = *p;
77 start = *p;
78 }
79 crate::path::PathCommand::LineTo(p) => {
80 if self.ray_intersects_segment(point, current, *p) {
81 crossings += 1;
82 }
83 current = *p;
84 }
85 crate::path::PathCommand::Close => {
86 if self.ray_intersects_segment(point, current, start) {
87 crossings += 1;
88 }
89 current = start;
90 }
91 crate::path::PathCommand::QuadTo { end, .. } => {
92 if self.ray_intersects_segment(point, current, *end) {
94 crossings += 1;
95 }
96 current = *end;
97 }
98 crate::path::PathCommand::CubicTo { end, .. } => {
99 if self.ray_intersects_segment(point, current, *end) {
101 crossings += 1;
102 }
103 current = *end;
104 }
105 crate::path::PathCommand::ArcTo { .. } => {
106 }
108 }
109 }
110
111 crossings % 2 == 1
113 }
114
115 fn ray_intersects_segment(&self, point: PointF, p1: PointF, p2: PointF) -> bool {
117 let py = point.y;
119 let px = point.x;
120
121 let (y_min, y_max) = if p1.y < p2.y {
123 (p1.y, p2.y)
124 } else {
125 (p2.y, p1.y)
126 };
127
128 if py < y_min || py >= y_max {
130 return false;
131 }
132
133 let t = (py - p1.y) / (p2.y - p1.y);
135 let x_intersect = p1.x + t * (p2.x - p1.x);
136
137 x_intersect >= px
139 }
140
141 pub fn bounds(&self) -> Option<IRect> {
143 match self {
144 ClipRegion::Rect(rect) => Some(*rect),
145 ClipRegion::Path(path) => path
146 .bounds()
147 .map(|r| IRect::new(r.x, r.y, r.width, r.height)),
148 ClipRegion::Empty => None,
149 }
150 }
151
152 pub fn is_empty(&self) -> bool {
154 matches!(self, ClipRegion::Empty)
155 }
156
157 pub fn intersect(&self, other: &ClipRegion) -> ClipRegion {
159 match (self, other) {
160 (ClipRegion::Empty, _) | (_, ClipRegion::Empty) => ClipRegion::Empty,
161 (ClipRegion::Rect(r1), ClipRegion::Rect(r2)) => {
162 let x1 = r1.x.max(r2.x);
164 let y1 = r1.y.max(r2.y);
165 let x2 = (r1.x + r1.width as i32).min(r2.x + r2.width as i32);
166 let y2 = (r1.y + r1.height as i32).min(r2.y + r2.height as i32);
167
168 if x1 < x2 && y1 < y2 {
169 ClipRegion::Rect(IRect::new(x1, y1, (x2 - x1) as u32, (y2 - y1) as u32))
170 } else {
171 ClipRegion::Empty
172 }
173 }
174 (ClipRegion::Rect(rect), ClipRegion::Path(path))
175 | (ClipRegion::Path(path), ClipRegion::Rect(rect)) => {
176 if let Some(path_bounds) = path.bounds() {
178 let path_irect = IRect::new(
179 path_bounds.x,
180 path_bounds.y,
181 path_bounds.width,
182 path_bounds.height,
183 );
184 ClipRegion::Rect(path_irect).intersect(&ClipRegion::Rect(*rect))
185 } else {
186 ClipRegion::Empty
187 }
188 }
189 (ClipRegion::Path(_), ClipRegion::Path(_)) => {
190 self.clone()
193 }
194 }
195 }
196
197 pub fn difference(&self, other: &ClipRegion) -> ClipRegion {
199 match (self, other) {
200 (ClipRegion::Empty, _) => ClipRegion::Empty,
201 (_, ClipRegion::Empty) => self.clone(),
202 _ => self.clone(),
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
213pub struct ClipStack {
214 stack: Vec<ClipRegion>,
216}
217
218impl ClipStack {
219 pub fn new() -> Self {
221 Self { stack: Vec::new() }
222 }
223
224 pub fn with_rect(rect: IRect) -> Self {
226 let mut stack = Self::new();
227 stack.push(ClipRegion::Rect(rect));
228 stack
229 }
230
231 pub fn push(&mut self, region: ClipRegion) {
233 self.stack.push(region);
234 }
235
236 pub fn pop(&mut self) -> Option<ClipRegion> {
238 self.stack.pop()
239 }
240
241 pub fn current(&self) -> Option<&ClipRegion> {
243 self.stack.last()
244 }
245
246 pub fn clip(&mut self, region: ClipRegion, op: ClipOp) {
248 if let Some(current) = self.stack.last() {
249 let new_region = match op {
250 ClipOp::Intersect => current.intersect(®ion),
251 ClipOp::Difference => current.difference(®ion),
252 };
253 self.stack.push(new_region);
254 } else {
255 self.stack.push(region);
257 }
258 }
259
260 pub fn clip_rect(&mut self, rect: IRect, op: ClipOp) {
262 self.clip(ClipRegion::Rect(rect), op);
263 }
264
265 pub fn clip_path(&mut self, path: Path, op: ClipOp) {
267 self.clip(ClipRegion::Path(path), op);
268 }
269
270 pub fn contains_point(&self, x: i32, y: i32) -> bool {
272 if let Some(region) = self.current() {
273 region.contains_point(x, y)
274 } else {
275 true }
277 }
278
279 pub fn bounds(&self) -> Option<IRect> {
281 self.current().and_then(|r| r.bounds())
282 }
283
284 pub fn is_empty(&self) -> bool {
286 self.stack.is_empty()
287 }
288
289 pub fn depth(&self) -> usize {
291 self.stack.len()
292 }
293
294 pub fn clear(&mut self) {
296 self.stack.clear();
297 }
298
299 pub fn save(&mut self) {
301 if let Some(current) = self.current() {
303 self.stack.push(current.clone());
304 }
305 }
306
307 pub fn restore(&mut self) {
309 self.pop();
310 }
311
312 pub fn get_bounds(&self) -> Option<Rect> {
314 self.bounds()
315 .map(|r| Rect::new(r.x, r.y, r.width, r.height))
316 }
317}
318
319impl Default for ClipStack {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_clip_region_rect() {
331 let rect = IRect::new(10, 10, 100, 100);
332 let region = ClipRegion::from_rect(rect);
333
334 assert!(region.contains_point(50, 50));
335 assert!(!region.contains_point(5, 5));
336 assert!(!region.contains_point(150, 150));
337 }
338
339 #[test]
340 fn test_clip_region_intersect() {
341 let rect1 = IRect::new(0, 0, 100, 100);
342 let rect2 = IRect::new(50, 50, 100, 100);
343
344 let region1 = ClipRegion::from_rect(rect1);
345 let region2 = ClipRegion::from_rect(rect2);
346
347 let intersection = region1.intersect(®ion2);
348
349 if let ClipRegion::Rect(result) = intersection {
350 assert_eq!(result.x, 50);
351 assert_eq!(result.y, 50);
352 assert_eq!(result.width, 50);
353 assert_eq!(result.height, 50);
354 } else {
355 panic!("Expected Rect region");
356 }
357 }
358
359 #[test]
360 fn test_clip_stack() {
361 let mut stack = ClipStack::new();
362 assert!(stack.is_empty());
363
364 let rect = IRect::new(0, 0, 100, 100);
365 stack.push(ClipRegion::from_rect(rect));
366 assert_eq!(stack.depth(), 1);
367
368 assert!(stack.contains_point(50, 50));
369 assert!(!stack.contains_point(150, 150));
370
371 stack.pop();
372 assert!(stack.is_empty());
373 }
374
375 #[test]
376 fn test_clip_stack_intersect() {
377 let mut stack = ClipStack::new();
378
379 let rect1 = IRect::new(0, 0, 100, 100);
380 stack.clip_rect(rect1, ClipOp::Intersect);
381
382 let rect2 = IRect::new(50, 50, 100, 100);
383 stack.clip_rect(rect2, ClipOp::Intersect);
384
385 assert!(stack.contains_point(75, 75));
387 assert!(!stack.contains_point(25, 25));
388 assert!(!stack.contains_point(125, 125));
389 }
390
391 #[test]
392 fn test_clip_region_bounds() {
393 let rect = IRect::new(10, 20, 100, 50);
394 let region = ClipRegion::from_rect(rect);
395 let bounds = region.bounds().unwrap();
396
397 assert_eq!(bounds.x, 10);
398 assert_eq!(bounds.y, 20);
399 assert_eq!(bounds.width, 100);
400 assert_eq!(bounds.height, 50);
401 }
402
403 #[test]
404 fn test_empty_clip_region() {
405 let region = ClipRegion::empty();
406 assert!(region.is_empty());
407 assert!(!region.contains_point(0, 0));
408 assert!(region.bounds().is_none());
409 }
410}