1use alloc::vec::Vec;
12use crate::os_lib::canvas2d::{is_point_in_path, parse_svg_path_into, FillRule, Matrix, PathBuilder, SubPath};
13
14#[derive(Clone, Debug, PartialEq)]
15pub enum ClipShape {
16 Circle { cx: i32, cy: i32, r_sq: i64 },
17 Ellipse { cx: i32, cy: i32, rx: i32, ry: i32 },
18 Inset { x0: i32, y0: i32, x1: i32, y1: i32, radius: (i32, i32, i32, i32) },
19 Polygon { points: Vec<(i32, i32)>, nonzero: bool },
20 Path { subpaths: Vec<SubPath>, nonzero: bool },
21}
22
23
24pub fn parse_clip_shape(css: &str, w: i32, h: i32) -> Option<ClipShape> {
27 parse_clip_shape_with_font(css, w, h, 16.0)
28}
29
30pub fn parse_clip_shape_with_font(css: &str, w: i32, h: i32, font_px: f32) -> Option<ClipShape> {
32 let s = css.trim();
33
34 if let Some(inner) = s.strip_prefix("circle(").and_then(|t| t.strip_suffix(')')) {
35 let mut parts = inner.splitn(2, "at");
36 let r_str = parts.next().unwrap_or("50%").trim();
37 let r = clip_len_with_font(r_str, w.min(h), font_px) as i32;
38 let (cx, cy) = if let Some(at) = parts.next() {
39 let mut ps = at.split_whitespace();
40 let cx = ps.next().map(|sv| clip_pos_with_font(sv, w, font_px)).unwrap_or(w / 2);
41 let cy = ps.next().map(|sv| clip_pos_with_font(sv, h, font_px)).unwrap_or(h / 2);
42 (cx, cy)
43 } else {
44 (w / 2, h / 2)
45 };
46 return Some(ClipShape::Circle { cx, cy, r_sq: (r as i64) * (r as i64) });
47 }
48
49 if let Some(inner) = s.strip_prefix("ellipse(").and_then(|t| t.strip_suffix(')')) {
50 let mut parts = inner.splitn(2, "at");
51 let radii_str = parts.next().unwrap_or("50% 50%").trim();
52 let mut radii = radii_str.split_whitespace();
53 let rx = radii.next().map(|sv| clip_len_with_font(sv, w, font_px) as i32).unwrap_or(w / 2);
54 let ry = radii.next().map(|sv| clip_len_with_font(sv, h, font_px) as i32).unwrap_or(h / 2);
55 let (cx, cy) = if let Some(at) = parts.next() {
56 let mut ps = at.split_whitespace();
57 let cx = ps.next().map(|sv| clip_pos_with_font(sv, w, font_px)).unwrap_or(w / 2);
58 let cy = ps.next().map(|sv| clip_pos_with_font(sv, h, font_px)).unwrap_or(h / 2);
59 (cx, cy)
60 } else {
61 (w / 2, h / 2)
62 };
63 return Some(ClipShape::Ellipse { cx, cy, rx, ry });
64 }
65
66 if let Some(inner) = s.strip_prefix("inset(").and_then(|t| t.strip_suffix(')')) {
67 let mut parts = inner.split("round");
68 let body = parts.next().unwrap_or(inner).trim();
69 let round_part = parts.next().map(|s| s.trim());
70 let mut vals = body.split_whitespace().map(|sv| clip_len_with_font(sv, w.max(h), font_px) as i32);
71 let top = vals.next().unwrap_or(0);
72 let right = vals.next().unwrap_or(top);
73 let bottom = vals.next().unwrap_or(top);
74 let left = vals.next().unwrap_or(right);
75
76 let radius = if let Some(r_str) = round_part {
77 let mut r_vals = r_str.split_whitespace().map(|sv| clip_len_with_font(sv, w.min(h), font_px) as i32);
78 let r_tl = r_vals.next().unwrap_or(0);
79 let r_tr = r_vals.next().unwrap_or(r_tl);
80 let r_br = r_vals.next().unwrap_or(r_tl);
81 let r_bl = r_vals.next().unwrap_or(r_tr);
82 (r_tl, r_tr, r_br, r_bl)
83 } else {
84 (0, 0, 0, 0)
85 };
86
87 return Some(ClipShape::Inset { x0: left, y0: top, x1: w - right, y1: h - bottom, radius });
88 }
89
90 if let Some(inner) = s.strip_prefix("polygon(").and_then(|t| t.strip_suffix(')')) {
91 let mut nonzero = false;
92 let mut pairs_src = inner;
93 if let Some(rest) = inner.trim_start().strip_prefix("nonzero") {
94 nonzero = true;
95 pairs_src = rest.trim_start().strip_prefix(',').unwrap_or(rest);
96 } else if let Some(rest) = inner.trim_start().strip_prefix("evenodd") {
97 pairs_src = rest.trim_start().strip_prefix(',').unwrap_or(rest);
98 }
99 let mut points = Vec::new();
100 for pair in pairs_src.split(',') {
101 let mut coords = pair.split_whitespace();
102 if let (Some(xs), Some(ys)) = (coords.next(), coords.next()) {
103 points.push((clip_len_with_font(xs, w, font_px) as i32, clip_len_with_font(ys, h, font_px) as i32));
104 }
105 }
106 if points.len() >= 3 {
107 return Some(ClipShape::Polygon { points, nonzero });
108 }
109 }
110
111 if let Some(inner) = s.strip_prefix("path(").and_then(|t| t.strip_suffix(')')) {
112 let trimmed = inner.trim();
113 let (nonzero, path_str) = if let Some(rest) = trimmed.strip_prefix("nonzero") {
114 let after_rule = rest.trim_start().strip_prefix(',').unwrap_or(rest).trim();
115 (true, after_rule)
116 } else if let Some(rest) = trimmed.strip_prefix("evenodd") {
117 let after_rule = rest.trim_start().strip_prefix(',').unwrap_or(rest).trim();
118 (false, after_rule)
119 } else {
120 (true, trimmed)
121 };
122
123 let d = if (path_str.starts_with('"') && path_str.ends_with('"'))
124 || (path_str.starts_with('\'') && path_str.ends_with('\''))
125 {
126 path_str.get(1..path_str.len().checked_sub(1)?)?
127 } else {
128 return None;
129 };
130
131 if d.trim().is_empty() {
132 return None;
133 }
134
135 let mut builder = PathBuilder::new();
136 parse_svg_path_into(d, &mut builder, &Matrix::identity());
137 let subpaths = builder.finish();
138 if !subpaths.is_empty() {
139 return Some(ClipShape::Path { subpaths, nonzero });
140 }
141 }
142
143 None
144}
145
146pub fn clip_inside(shape: &ClipShape, ox: i32, oy: i32) -> bool {
150 match shape {
151 ClipShape::Circle { cx, cy, r_sq } => {
152 let dx = (ox - cx) as i64;
153 let dy = (oy - cy) as i64;
154 dx * dx + dy * dy <= *r_sq
155 }
156 ClipShape::Ellipse { cx, cy, rx, ry } => {
157 if *rx == 0 || *ry == 0 { return false; }
158 let dx = (ox - cx) as f32 / (*rx as f32);
159 let dy = (oy - cy) as f32 / (*ry as f32);
160 dx * dx + dy * dy <= 1.0
161 }
162 ClipShape::Inset { x0, y0, x1, y1, radius } => {
163 if ox >= *x0 && ox < *x1 && oy >= *y0 && oy < *y1 {
164 if *radius == (0, 0, 0, 0) {
165 true
166 } else {
167 let rx = ox - *x0;
168 let ry = oy - *y0;
169 let iw = *x1 - *x0;
170 let ih = *y1 - *y0;
171 point_in_rounded_rect(rx, ry, iw, ih, *radius)
172 }
173 } else {
174 false
175 }
176 }
177 ClipShape::Polygon { points, nonzero } => {
178 if *nonzero {
179 polygon_contains_nonzero(points, ox, oy)
180 } else {
181 polygon_contains(points, ox, oy)
182 }
183 }
184 ClipShape::Path { subpaths, nonzero } => {
185 let rule = if *nonzero { FillRule::NonZero } else { FillRule::EvenOdd };
186 is_point_in_path(subpaths, ox as f32 + 0.5, oy as f32 + 0.5, rule)
187 }
188 }
189}
190
191
192fn polygon_contains(points: &[(i32, i32)], px: i32, py: i32) -> bool {
195 let n = points.len();
196 if n < 3 { return false; }
197 let mut inside = false;
198 let mut j = n - 1;
199 for i in 0..n {
200 let (xi, yi) = points[i];
201 let (xj, yj) = points[j];
202 if (yi > py) != (yj > py) && yj != yi {
203 let x_intersect = (xj - xi) * (py - yi) / (yj - yi) + xi;
204 if px < x_intersect {
205 inside = !inside;
206 }
207 }
208 j = i;
209 }
210 inside
211}
212
213fn polygon_contains_nonzero(points: &[(i32, i32)], px: i32, py: i32) -> bool {
216 let n = points.len();
217 if n < 3 { return false; }
218 let mut winding = 0i32;
219 let mut j = n - 1;
220 for i in 0..n {
221 let (xi, yi) = points[i];
222 let (xj, yj) = points[j];
223 if yj <= py {
224 if yi > py {
225 let cross = (xi - xj) * (py - yj) - (px - xj) * (yi - yj);
226 if cross > 0 {
227 winding += 1;
228 }
229 }
230 } else if yi <= py {
231 let cross = (xi - xj) * (py - yj) - (px - xj) * (yi - yj);
232 if cross < 0 {
233 winding -= 1;
234 }
235 }
236 j = i;
237 }
238 winding != 0
239}
240
241pub fn clip_pos(s: &str, base: i32) -> i32 {
242 clip_pos_with_font(s, base, 16.0)
243}
244
245pub fn clip_pos_with_font(s: &str, base: i32, font_px: f32) -> i32 {
246 match s.trim().to_ascii_lowercase().as_str() {
247 "left" | "top" => 0,
248 "right" | "bottom" => base,
249 "center" => base / 2,
250 _ => clip_len_with_font(s, base, font_px) as i32,
251 }
252}
253
254pub fn clip_len(s: &str, base: i32) -> f32 {
255 clip_len_with_font(s, base, 16.0)
256}
257
258pub fn clip_len_with_font(s: &str, base: i32, font_px: f32) -> f32 {
259 let s = s.trim();
260 let font = if font_px > 0.0 { font_px } else { 16.0 };
261 if let Some(pct) = s.strip_suffix('%') {
262 pct.trim().parse::<f32>().unwrap_or(50.0) * base as f32 / 100.0
263 } else if let Some(rem) = s.strip_suffix("rem") {
264 rem.trim().parse::<f32>().unwrap_or(0.0) * 16.0
265 } else if let Some(em) = s.strip_suffix("em") {
266 em.trim().parse::<f32>().unwrap_or(0.0) * font
267 } else if let Some(px) = s.strip_suffix("px") {
268 px.trim().parse::<f32>().unwrap_or(0.0)
269 } else if let Some(pt) = s.strip_suffix("pt") {
270 pt.trim().parse::<f32>().unwrap_or(0.0) * 4.0 / 3.0
271 } else {
272 s.parse::<f32>().unwrap_or(0.0)
273 }
274}
275
276pub fn point_in_rounded_rect(px: i32, py: i32, w: i32, h: i32, r: (i32, i32, i32, i32)) -> bool {
277 if px < 0 || py < 0 || px >= w || py >= h {
278 return false;
279 }
280
281 let tl = r.0.min(w / 2).min(h / 2).max(0);
282 let tr = r.1.min(w / 2).min(h / 2).max(0);
283 let br = r.2.min(w / 2).min(h / 2).max(0);
284 let bl = r.3.min(w / 2).min(h / 2).max(0);
285
286 if tl > 0 && px < tl && py < tl {
287 let dx = (px - tl) as i64;
288 let dy = (py - tl) as i64;
289 return dx * dx + dy * dy <= (tl as i64) * (tl as i64);
290 }
291 if tr > 0 && px >= w - tr && py < tr {
292 let dx = (px - (w - tr)) as i64;
293 let dy = (py - tr) as i64;
294 return dx * dx + dy * dy <= (tr as i64) * (tr as i64);
295 }
296 if bl > 0 && px < bl && py >= h - bl {
297 let dx = (px - bl) as i64;
298 let dy = (py - (h - bl)) as i64;
299 return dx * dx + dy * dy <= (bl as i64) * (bl as i64);
300 }
301 if br > 0 && px >= w - br && py >= h - br {
302 let dx = (px - (w - br)) as i64;
303 let dy = (py - (h - br)) as i64;
304 return dx * dx + dy * dy <= (br as i64) * (br as i64);
305 }
306 true
307}