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