1use crate::corety::{AzString, OptionF32};
6
7#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
9#[repr(C)]
10pub struct ShapePoint {
11 pub x: f32,
12 pub y: f32,
13}
14
15impl_option!(
16 ShapePoint,
17 OptionShapePoint,
18 [Debug, Copy, Clone, PartialEq, PartialOrd]
19);
20
21impl ShapePoint {
22 pub const fn new(x: f32, y: f32) -> Self {
23 Self { x, y }
24 }
25
26 pub const fn zero() -> Self {
27 Self { x: 0.0, y: 0.0 }
28 }
29}
30
31impl Eq for ShapePoint {}
32
33impl Ord for ShapePoint {
34 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
35 match self.x.partial_cmp(&other.x) {
36 Some(core::cmp::Ordering::Equal) => self
37 .y
38 .partial_cmp(&other.y)
39 .unwrap_or(core::cmp::Ordering::Equal),
40 other => other.unwrap_or(core::cmp::Ordering::Equal),
41 }
42 }
43}
44
45impl core::hash::Hash for ShapePoint {
46 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
47 self.x.to_bits().hash(state);
48 self.y.to_bits().hash(state);
49 }
50}
51
52impl_vec!(ShapePoint, ShapePointVec, ShapePointVecDestructor, ShapePointVecDestructorType, ShapePointVecSlice, OptionShapePoint);
53impl_vec_debug!(ShapePoint, ShapePointVec);
54impl_vec_partialord!(ShapePoint, ShapePointVec);
55impl_vec_ord!(ShapePoint, ShapePointVec);
56impl_vec_clone!(ShapePoint, ShapePointVec, ShapePointVecDestructor);
57impl_vec_partialeq!(ShapePoint, ShapePointVec);
58impl_vec_eq!(ShapePoint, ShapePointVec);
59impl_vec_hash!(ShapePoint, ShapePointVec);
60
61#[derive(Debug, Clone, PartialEq)]
63#[repr(C)]
64pub struct ShapeCircle {
65 pub center: ShapePoint,
66 pub radius: f32,
67}
68
69impl Eq for ShapeCircle {}
70impl core::hash::Hash for ShapeCircle {
71 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
72 self.center.hash(state);
73 self.radius.to_bits().hash(state);
74 }
75}
76impl PartialOrd for ShapeCircle {
77 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
78 Some(self.cmp(other))
79 }
80}
81impl Ord for ShapeCircle {
82 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
83 match self.center.cmp(&other.center) {
84 core::cmp::Ordering::Equal => self
85 .radius
86 .partial_cmp(&other.radius)
87 .unwrap_or(core::cmp::Ordering::Equal),
88 other => other,
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq)]
95#[repr(C)]
96pub struct ShapeEllipse {
97 pub center: ShapePoint,
98 pub radius_x: f32,
99 pub radius_y: f32,
100}
101
102impl Eq for ShapeEllipse {}
103impl core::hash::Hash for ShapeEllipse {
104 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
105 self.center.hash(state);
106 self.radius_x.to_bits().hash(state);
107 self.radius_y.to_bits().hash(state);
108 }
109}
110impl PartialOrd for ShapeEllipse {
111 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
112 Some(self.cmp(other))
113 }
114}
115impl Ord for ShapeEllipse {
116 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
117 match self.center.cmp(&other.center) {
118 core::cmp::Ordering::Equal => match self.radius_x.partial_cmp(&other.radius_x) {
119 Some(core::cmp::Ordering::Equal) | None => self
120 .radius_y
121 .partial_cmp(&other.radius_y)
122 .unwrap_or(core::cmp::Ordering::Equal),
123 Some(other) => other,
124 },
125 other => other,
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
132#[repr(C)]
133pub struct ShapePolygon {
134 pub points: ShapePointVec,
135}
136
137#[derive(Debug, Clone, PartialEq)]
140#[repr(C)]
141pub struct ShapeInset {
142 pub inset_top: f32,
143 pub inset_right: f32,
144 pub inset_bottom: f32,
145 pub inset_left: f32,
146 pub border_radius: OptionF32,
147}
148
149impl Eq for ShapeInset {}
150impl core::hash::Hash for ShapeInset {
151 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
152 self.inset_top.to_bits().hash(state);
153 self.inset_right.to_bits().hash(state);
154 self.inset_bottom.to_bits().hash(state);
155 self.inset_left.to_bits().hash(state);
156 self.border_radius.hash(state);
157 }
158}
159impl PartialOrd for ShapeInset {
160 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
161 Some(self.cmp(other))
162 }
163}
164impl Ord for ShapeInset {
165 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
166 match self.inset_top.partial_cmp(&other.inset_top) {
167 Some(core::cmp::Ordering::Equal) | None => {
168 match self.inset_right.partial_cmp(&other.inset_right) {
169 Some(core::cmp::Ordering::Equal) | None => {
170 match self.inset_bottom.partial_cmp(&other.inset_bottom) {
171 Some(core::cmp::Ordering::Equal) | None => {
172 match self.inset_left.partial_cmp(&other.inset_left) {
173 Some(core::cmp::Ordering::Equal) | None => {
174 self.border_radius.cmp(&other.border_radius)
175 }
176 Some(other) => other,
177 }
178 }
179 Some(other) => other,
180 }
181 }
182 Some(other) => other,
183 }
184 }
185 Some(other) => other,
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
192#[repr(C)]
193pub struct ShapePath {
194 pub data: AzString,
195}
196
197#[derive(Debug, Clone, PartialEq)]
200#[repr(C, u8)]
201pub enum CssShape {
202 Circle(ShapeCircle),
203 Ellipse(ShapeEllipse),
204 Polygon(ShapePolygon),
205 Inset(ShapeInset),
206 Path(ShapePath),
207}
208
209impl Eq for CssShape {}
210
211impl core::hash::Hash for CssShape {
212 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
213 core::mem::discriminant(self).hash(state);
214 match self {
215 CssShape::Circle(c) => c.hash(state),
216 CssShape::Ellipse(e) => e.hash(state),
217 CssShape::Polygon(p) => p.hash(state),
218 CssShape::Inset(i) => i.hash(state),
219 CssShape::Path(p) => p.hash(state),
220 }
221 }
222}
223
224impl PartialOrd for CssShape {
225 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
226 Some(self.cmp(other))
227 }
228}
229
230impl Ord for CssShape {
231 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
232 match (self, other) {
233 (CssShape::Circle(a), CssShape::Circle(b)) => a.cmp(b),
234 (CssShape::Ellipse(a), CssShape::Ellipse(b)) => a.cmp(b),
235 (CssShape::Polygon(a), CssShape::Polygon(b)) => a.cmp(b),
236 (CssShape::Inset(a), CssShape::Inset(b)) => a.cmp(b),
237 (CssShape::Path(a), CssShape::Path(b)) => a.cmp(b),
238 (CssShape::Circle(_), _) => core::cmp::Ordering::Less,
240 (_, CssShape::Circle(_)) => core::cmp::Ordering::Greater,
241 (CssShape::Ellipse(_), _) => core::cmp::Ordering::Less,
242 (_, CssShape::Ellipse(_)) => core::cmp::Ordering::Greater,
243 (CssShape::Polygon(_), _) => core::cmp::Ordering::Less,
244 (_, CssShape::Polygon(_)) => core::cmp::Ordering::Greater,
245 (CssShape::Inset(_), CssShape::Path(_)) => core::cmp::Ordering::Less,
246 (CssShape::Path(_), CssShape::Inset(_)) => core::cmp::Ordering::Greater,
247 }
248 }
249}
250
251impl CssShape {
252 pub fn circle(center: ShapePoint, radius: f32) -> Self {
254 CssShape::Circle(ShapeCircle { center, radius })
255 }
256
257 pub fn ellipse(center: ShapePoint, radius_x: f32, radius_y: f32) -> Self {
259 CssShape::Ellipse(ShapeEllipse {
260 center,
261 radius_x,
262 radius_y,
263 })
264 }
265
266 pub fn polygon(points: ShapePointVec) -> Self {
268 CssShape::Polygon(ShapePolygon { points })
269 }
270
271 pub fn inset(top: f32, right: f32, bottom: f32, left: f32) -> Self {
273 CssShape::Inset(ShapeInset {
274 inset_top: top,
275 inset_right: right,
276 inset_bottom: bottom,
277 inset_left: left,
278 border_radius: OptionF32::None,
279 })
280 }
281
282 pub fn inset_rounded(top: f32, right: f32, bottom: f32, left: f32, radius: f32) -> Self {
284 CssShape::Inset(ShapeInset {
285 inset_top: top,
286 inset_right: right,
287 inset_bottom: bottom,
288 inset_left: left,
289 border_radius: OptionF32::Some(radius),
290 })
291 }
292}
293
294impl_option!(
295 CssShape,
296 OptionCssShape,
297 copy = false,
298 [Debug, Clone, PartialEq]
299);
300
301#[derive(Debug, Clone, Copy, PartialEq)]
304#[repr(C)]
305pub struct LineSegment {
306 pub start_x: f32,
308
309 pub width: f32,
311
312 pub priority: i32,
314}
315
316impl_option!(
317 LineSegment,
318 OptionLineSegment,
319 [Debug, Copy, Clone, PartialEq]
320);
321
322impl LineSegment {
323 pub const fn new(start_x: f32, width: f32) -> Self {
325 Self {
326 start_x,
327 width,
328 priority: 0,
329 }
330 }
331
332 #[inline]
334 pub fn end_x(&self) -> f32 {
335 self.start_x + self.width
336 }
337
338 pub fn overlaps(&self, other: &Self) -> bool {
340 self.start_x < other.end_x() && other.start_x < self.end_x()
341 }
342
343 pub fn intersection(&self, other: &Self) -> Option<Self> {
345 let start = self.start_x.max(other.start_x);
346 let end = self.end_x().min(other.end_x());
347
348 if start < end {
349 Some(Self {
350 start_x: start,
351 width: end - start,
352 priority: self.priority.max(other.priority),
353 })
354 } else {
355 None
356 }
357 }
358}
359
360impl_vec!(LineSegment, LineSegmentVec, LineSegmentVecDestructor, LineSegmentVecDestructorType, LineSegmentVecSlice, OptionLineSegment);
361impl_vec_debug!(LineSegment, LineSegmentVec);
362impl_vec_clone!(LineSegment, LineSegmentVec, LineSegmentVecDestructor);
363impl_vec_partialeq!(LineSegment, LineSegmentVec);
364
365#[derive(Debug, Copy, Clone, PartialEq)]
367#[repr(C)]
368pub struct ShapeRect {
369 pub origin: ShapePoint,
370 pub width: f32,
371 pub height: f32,
372}
373
374impl ShapeRect {
375 pub const fn new(origin: ShapePoint, width: f32, height: f32) -> Self {
376 Self {
377 origin,
378 width,
379 height,
380 }
381 }
382
383 pub const fn zero() -> Self {
384 Self {
385 origin: ShapePoint::zero(),
386 width: 0.0,
387 height: 0.0,
388 }
389 }
390}
391
392impl_option!(ShapeRect, OptionShapeRect, [Debug, Copy, Clone, PartialEq]);
393
394impl CssShape {
395 pub fn bounding_box(&self) -> ShapeRect {
397 match self {
398 CssShape::Circle(ShapeCircle { center, radius }) => ShapeRect {
399 origin: ShapePoint::new(center.x - radius, center.y - radius),
400 width: radius * 2.0,
401 height: radius * 2.0,
402 },
403
404 CssShape::Ellipse(ShapeEllipse {
405 center,
406 radius_x,
407 radius_y,
408 }) => ShapeRect {
409 origin: ShapePoint::new(center.x - radius_x, center.y - radius_y),
410 width: radius_x * 2.0,
411 height: radius_y * 2.0,
412 },
413
414 CssShape::Polygon(ShapePolygon { points }) => {
415 if points.as_ref().is_empty() {
416 return ShapeRect::zero();
417 }
418
419 let first = points.as_ref()[0];
420 let mut min_x = first.x;
421 let mut min_y = first.y;
422 let mut max_x = first.x;
423 let mut max_y = first.y;
424
425 for point in points.as_ref().iter().skip(1) {
426 min_x = min_x.min(point.x);
427 min_y = min_y.min(point.y);
428 max_x = max_x.max(point.x);
429 max_y = max_y.max(point.y);
430 }
431
432 ShapeRect {
433 origin: ShapePoint::new(min_x, min_y),
434 width: max_x - min_x,
435 height: max_y - min_y,
436 }
437 }
438
439 CssShape::Inset(ShapeInset {
440 inset_top,
441 inset_right,
442 inset_bottom,
443 inset_left,
444 ..
445 }) => {
446 ShapeRect {
449 origin: ShapePoint::new(*inset_left, *inset_top),
450 width: 0.0, height: 0.0,
452 }
453 }
454
455 CssShape::Path(_) => {
456 ShapeRect::zero()
459 }
460 }
461 }
462
463 pub fn compute_line_segments(
474 &self,
475 y: f32,
476 margin: f32,
477 reference_box: OptionShapeRect,
478 ) -> LineSegmentVec {
479 use alloc::vec::Vec;
480
481 let segments: Vec<LineSegment> = match self {
482 CssShape::Circle(ShapeCircle { center, radius }) => {
483 let dy = y - center.y;
484 let r_with_margin = radius - margin;
485
486 if dy.abs() > r_with_margin {
487 Vec::new() } else {
489 let half_width = (r_with_margin.powi(2) - dy.powi(2)).sqrt();
491
492 alloc::vec![LineSegment {
493 start_x: center.x - half_width,
494 width: 2.0 * half_width,
495 priority: 0,
496 }]
497 }
498 }
499
500 CssShape::Ellipse(ShapeEllipse {
501 center,
502 radius_x,
503 radius_y,
504 }) => {
505 let dy = y - center.y;
506 let ry_with_margin = radius_y - margin;
507
508 if dy.abs() > ry_with_margin {
509 Vec::new() } else {
511 let ratio = dy / ry_with_margin;
514 let factor = (1.0 - ratio.powi(2)).sqrt();
515 let half_width = (radius_x - margin) * factor;
516
517 alloc::vec![LineSegment {
518 start_x: center.x - half_width,
519 width: 2.0 * half_width,
520 priority: 0,
521 }]
522 }
523 }
524
525 CssShape::Polygon(ShapePolygon { points }) => {
526 compute_polygon_line_segments(points.as_ref(), y, margin)
527 }
528
529 CssShape::Inset(ShapeInset {
530 inset_top: top,
531 inset_right: right,
532 inset_bottom: bottom,
533 inset_left: left,
534 border_radius,
535 }) => {
536 let ref_box = match reference_box {
537 OptionShapeRect::Some(r) => r,
538 OptionShapeRect::None => ShapeRect::zero(),
539 };
540
541 let inset_top = ref_box.origin.y + top + margin;
542 let inset_bottom = ref_box.origin.y + ref_box.height - bottom - margin;
543 let inset_left = ref_box.origin.x + left + margin;
544 let inset_right = ref_box.origin.x + ref_box.width - right - margin;
545
546 if y < inset_top || y > inset_bottom {
547 Vec::new()
548 } else {
549 alloc::vec![LineSegment {
552 start_x: inset_left,
553 width: inset_right - inset_left,
554 priority: 0,
555 }]
556 }
557 }
558
559 CssShape::Path(_) => {
560 Vec::new()
563 }
564 };
565
566 segments.into()
567 }
568}
569
570fn compute_polygon_line_segments(
573 points: &[ShapePoint],
574 y: f32,
575 margin: f32,
576) -> alloc::vec::Vec<LineSegment> {
577 use alloc::vec::Vec;
578
579 if points.len() < 3 {
580 return Vec::new();
581 }
582
583 let mut intersections = Vec::new();
585
586 for i in 0..points.len() {
587 let p1 = points[i];
588 let p2 = points[(i + 1) % points.len()];
589
590 let min_y = p1.y.min(p2.y);
592 let max_y = p1.y.max(p2.y);
593
594 if y >= min_y && y < max_y {
595 let t = (y - p1.y) / (p2.y - p1.y);
597 let x = p1.x + t * (p2.x - p1.x);
598 intersections.push(x);
599 }
600 }
601
602 intersections.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
604
605 let mut segments = Vec::new();
608
609 for chunk in intersections.chunks(2) {
610 if chunk.len() == 2 {
611 let start = chunk[0] + margin;
612 let end = chunk[1] - margin;
613
614 if start < end {
615 segments.push(LineSegment {
616 start_x: start,
617 width: end - start,
618 priority: 0,
619 });
620 }
621 }
622 }
623
624 segments
625}