1
//! CSS Shape data structures for shape-inside, shape-outside, and clip-path
2
//!
3
//! These types are C-compatible (repr(C)) for use across FFI boundaries.
4

            
5
use alloc::string::String;
6
use crate::corety::{AzString, OptionF32};
7

            
8
/// Compares two f32 values for ordering, treating NaN as equal.
9
fn cmp_f32(a: f32, b: f32) -> core::cmp::Ordering {
10
    a.partial_cmp(&b).unwrap_or(core::cmp::Ordering::Equal)
11
}
12

            
13
/// A 2D point for shape coordinates (using f32 for precision)
14
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
15
#[repr(C)]
16
pub struct ShapePoint {
17
    pub x: f32,
18
    pub y: f32,
19
}
20

            
21
impl_option!(
22
    ShapePoint,
23
    OptionShapePoint,
24
    [Debug, Copy, Clone, PartialEq, PartialOrd]
25
);
26

            
27
impl ShapePoint {
28
21
    pub const fn new(x: f32, y: f32) -> Self {
29
21
        Self { x, y }
30
21
    }
31

            
32
2
    pub const fn zero() -> Self {
33
2
        Self { x: 0.0, y: 0.0 }
34
2
    }
35
}
36

            
37
impl Eq for ShapePoint {}
38

            
39
impl Ord for ShapePoint {
40
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
41
        match self.x.partial_cmp(&other.x) {
42
            Some(core::cmp::Ordering::Equal) => self
43
                .y
44
                .partial_cmp(&other.y)
45
                .unwrap_or(core::cmp::Ordering::Equal),
46
            other => other.unwrap_or(core::cmp::Ordering::Equal),
47
        }
48
    }
49
}
50

            
51
impl core::hash::Hash for ShapePoint {
52
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
53
        self.x.to_bits().hash(state);
54
        self.y.to_bits().hash(state);
55
    }
56
}
57

            
58
impl_vec!(ShapePoint, ShapePointVec, ShapePointVecDestructor, ShapePointVecDestructorType, ShapePointVecSlice, OptionShapePoint);
59
impl_vec_debug!(ShapePoint, ShapePointVec);
60
impl_vec_partialord!(ShapePoint, ShapePointVec);
61
impl_vec_ord!(ShapePoint, ShapePointVec);
62
impl_vec_clone!(ShapePoint, ShapePointVec, ShapePointVecDestructor);
63
impl_vec_partialeq!(ShapePoint, ShapePointVec);
64
impl_vec_eq!(ShapePoint, ShapePointVec);
65
impl_vec_hash!(ShapePoint, ShapePointVec);
66

            
67
/// A circle shape defined by center point and radius
68
#[derive(Debug, Clone, PartialEq)]
69
#[repr(C)]
70
pub struct ShapeCircle {
71
    pub center: ShapePoint,
72
    pub radius: f32,
73
}
74

            
75
impl Eq for ShapeCircle {}
76
impl core::hash::Hash for ShapeCircle {
77
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
78
        self.center.hash(state);
79
        self.radius.to_bits().hash(state);
80
    }
81
}
82
impl PartialOrd for ShapeCircle {
83
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
84
        Some(self.cmp(other))
85
    }
86
}
87
impl Ord for ShapeCircle {
88
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
89
        match self.center.cmp(&other.center) {
90
            core::cmp::Ordering::Equal => self
91
                .radius
92
                .partial_cmp(&other.radius)
93
                .unwrap_or(core::cmp::Ordering::Equal),
94
            other => other,
95
        }
96
    }
97
}
98

            
99
/// An ellipse shape defined by center point and two radii
100
#[derive(Debug, Clone, PartialEq)]
101
#[repr(C)]
102
pub struct ShapeEllipse {
103
    pub center: ShapePoint,
104
    pub radius_x: f32,
105
    pub radius_y: f32,
106
}
107

            
108
impl Eq for ShapeEllipse {}
109
impl core::hash::Hash for ShapeEllipse {
110
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
111
        self.center.hash(state);
112
        self.radius_x.to_bits().hash(state);
113
        self.radius_y.to_bits().hash(state);
114
    }
115
}
116
impl PartialOrd for ShapeEllipse {
117
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
118
        Some(self.cmp(other))
119
    }
120
}
121
impl Ord for ShapeEllipse {
122
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
123
        match self.center.cmp(&other.center) {
124
            core::cmp::Ordering::Equal => match self.radius_x.partial_cmp(&other.radius_x) {
125
                Some(core::cmp::Ordering::Equal) | None => self
126
                    .radius_y
127
                    .partial_cmp(&other.radius_y)
128
                    .unwrap_or(core::cmp::Ordering::Equal),
129
                Some(other) => other,
130
            },
131
            other => other,
132
        }
133
    }
134
}
135

            
136
/// A polygon shape defined by a list of points (in clockwise order)
137
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
138
#[repr(C)]
139
pub struct ShapePolygon {
140
    pub points: ShapePointVec,
141
}
142

            
143
/// An inset rectangle with optional border radius
144
/// Defined by insets from the reference box edges
145
#[derive(Debug, Clone, PartialEq)]
146
#[repr(C)]
147
pub struct ShapeInset {
148
    pub inset_top: f32,
149
    pub inset_right: f32,
150
    pub inset_bottom: f32,
151
    pub inset_left: f32,
152
    pub border_radius: OptionF32,
153
}
154

            
155
impl Eq for ShapeInset {}
156
impl core::hash::Hash for ShapeInset {
157
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
158
        self.inset_top.to_bits().hash(state);
159
        self.inset_right.to_bits().hash(state);
160
        self.inset_bottom.to_bits().hash(state);
161
        self.inset_left.to_bits().hash(state);
162
        self.border_radius.hash(state);
163
    }
164
}
165
impl PartialOrd for ShapeInset {
166
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
167
        Some(self.cmp(other))
168
    }
169
}
170
impl Ord for ShapeInset {
171
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
172
        cmp_f32(self.inset_top, other.inset_top)
173
            .then_with(|| cmp_f32(self.inset_right, other.inset_right))
174
            .then_with(|| cmp_f32(self.inset_bottom, other.inset_bottom))
175
            .then_with(|| cmp_f32(self.inset_left, other.inset_left))
176
            .then_with(|| self.border_radius.cmp(&other.border_radius))
177
    }
178
}
179

            
180
/// An SVG-like path for shape definitions.
181
/// TODO: path parsing is not yet implemented — `data` is stored but not interpreted.
182
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
183
#[repr(C)]
184
pub struct ShapePath {
185
    pub data: AzString,
186
}
187

            
188
/// Represents a CSS shape for shape-inside, shape-outside, and clip-path.
189
/// Used for both text layout (shape-inside/outside) and rendering clipping (clip-path).
190
#[derive(Debug, Clone, PartialEq)]
191
#[repr(C, u8)]
192
pub enum CssShape {
193
    Circle(ShapeCircle),
194
    Ellipse(ShapeEllipse),
195
    Polygon(ShapePolygon),
196
    Inset(ShapeInset),
197
    Path(ShapePath),
198
}
199

            
200
impl Eq for CssShape {}
201

            
202
impl core::hash::Hash for CssShape {
203
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
204
        core::mem::discriminant(self).hash(state);
205
        match self {
206
            CssShape::Circle(c) => c.hash(state),
207
            CssShape::Ellipse(e) => e.hash(state),
208
            CssShape::Polygon(p) => p.hash(state),
209
            CssShape::Inset(i) => i.hash(state),
210
            CssShape::Path(p) => p.hash(state),
211
        }
212
    }
213
}
214

            
215
impl PartialOrd for CssShape {
216
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
217
        Some(self.cmp(other))
218
    }
219
}
220

            
221
impl Ord for CssShape {
222
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
223
        match (self, other) {
224
            (CssShape::Circle(a), CssShape::Circle(b)) => a.cmp(b),
225
            (CssShape::Ellipse(a), CssShape::Ellipse(b)) => a.cmp(b),
226
            (CssShape::Polygon(a), CssShape::Polygon(b)) => a.cmp(b),
227
            (CssShape::Inset(a), CssShape::Inset(b)) => a.cmp(b),
228
            (CssShape::Path(a), CssShape::Path(b)) => a.cmp(b),
229
            // Different variants: use discriminant ordering
230
            (CssShape::Circle(_), _) => core::cmp::Ordering::Less,
231
            (_, CssShape::Circle(_)) => core::cmp::Ordering::Greater,
232
            (CssShape::Ellipse(_), _) => core::cmp::Ordering::Less,
233
            (_, CssShape::Ellipse(_)) => core::cmp::Ordering::Greater,
234
            (CssShape::Polygon(_), _) => core::cmp::Ordering::Less,
235
            (_, CssShape::Polygon(_)) => core::cmp::Ordering::Greater,
236
            (CssShape::Inset(_), CssShape::Path(_)) => core::cmp::Ordering::Less,
237
            (CssShape::Path(_), CssShape::Inset(_)) => core::cmp::Ordering::Greater,
238
        }
239
    }
240
}
241

            
242
impl CssShape {
243
    /// Creates a circle shape at the given position with the given radius
244
4
    pub fn circle(center: ShapePoint, radius: f32) -> Self {
245
4
        CssShape::Circle(ShapeCircle { center, radius })
246
4
    }
247

            
248
    /// Creates an ellipse shape
249
1
    pub fn ellipse(center: ShapePoint, radius_x: f32, radius_y: f32) -> Self {
250
1
        CssShape::Ellipse(ShapeEllipse {
251
1
            center,
252
1
            radius_x,
253
1
            radius_y,
254
1
        })
255
1
    }
256

            
257
    /// Creates a polygon from a list of points
258
3
    pub fn polygon(points: ShapePointVec) -> Self {
259
3
        CssShape::Polygon(ShapePolygon { points })
260
3
    }
261

            
262
    /// Creates an inset rectangle
263
1
    pub fn inset(top: f32, right: f32, bottom: f32, left: f32) -> Self {
264
1
        CssShape::Inset(ShapeInset {
265
1
            inset_top: top,
266
1
            inset_right: right,
267
1
            inset_bottom: bottom,
268
1
            inset_left: left,
269
1
            border_radius: OptionF32::None,
270
1
        })
271
1
    }
272

            
273
    /// Creates an inset rectangle with rounded corners
274
1
    pub fn inset_rounded(top: f32, right: f32, bottom: f32, left: f32, radius: f32) -> Self {
275
1
        CssShape::Inset(ShapeInset {
276
1
            inset_top: top,
277
1
            inset_right: right,
278
1
            inset_bottom: bottom,
279
1
            inset_left: left,
280
1
            border_radius: OptionF32::Some(radius),
281
1
        })
282
1
    }
283

            
284
    pub fn print_as_css_value(&self) -> String {
285
        use alloc::format;
286
        match self {
287
            CssShape::Circle(ShapeCircle { center, radius }) => {
288
                format!("circle({}px at {}px {}px)", radius, center.x, center.y)
289
            }
290
            CssShape::Ellipse(ShapeEllipse { center, radius_x, radius_y }) => {
291
                format!("ellipse({}px {}px at {}px {}px)", radius_x, radius_y, center.x, center.y)
292
            }
293
            CssShape::Polygon(ShapePolygon { points }) => {
294
                let pts: alloc::vec::Vec<String> = points.as_ref().iter()
295
                    .map(|p| format!("{}px {}px", p.x, p.y))
296
                    .collect();
297
                format!("polygon({})", pts.join(", "))
298
            }
299
            CssShape::Inset(ShapeInset { inset_top, inset_right, inset_bottom, inset_left, border_radius }) => {
300
                let base = format!("inset({}px {}px {}px {}px", inset_top, inset_right, inset_bottom, inset_left);
301
                match border_radius {
302
                    OptionF32::Some(r) => format!("{} round {}px)", base, r),
303
                    OptionF32::None => format!("{})", base),
304
                }
305
            }
306
            CssShape::Path(ShapePath { data }) => {
307
                format!("path(\"{}\")", data.as_str())
308
            }
309
        }
310
    }
311

            
312
    pub fn format_as_rust_code(&self) -> String {
313
        use alloc::format;
314
        match self {
315
            CssShape::Circle(ShapeCircle { center, radius }) => {
316
                format!(
317
                    "CssShape::Circle(ShapeCircle {{ center: ShapePoint::new({}_f32, {}_f32), radius: {}_f32 }})",
318
                    center.x, center.y, radius
319
                )
320
            }
321
            CssShape::Ellipse(ShapeEllipse { center, radius_x, radius_y }) => {
322
                format!(
323
                    "CssShape::Ellipse(ShapeEllipse {{ center: ShapePoint::new({}_f32, {}_f32), radius_x: {}_f32, radius_y: {}_f32 }})",
324
                    center.x, center.y, radius_x, radius_y
325
                )
326
            }
327
            CssShape::Polygon(ShapePolygon { points }) => {
328
                let pts: alloc::vec::Vec<String> = points.as_ref().iter()
329
                    .map(|p| format!("ShapePoint::new({}_f32, {}_f32)", p.x, p.y))
330
                    .collect();
331
                format!("CssShape::Polygon(ShapePolygon {{ points: vec![{}].into() }})", pts.join(", "))
332
            }
333
            CssShape::Inset(ShapeInset { inset_top, inset_right, inset_bottom, inset_left, border_radius }) => {
334
                let br = match border_radius {
335
                    OptionF32::Some(r) => format!("OptionF32::Some({}_f32)", r),
336
                    OptionF32::None => String::from("OptionF32::None"),
337
                };
338
                format!(
339
                    "CssShape::Inset(ShapeInset {{ inset_top: {}_f32, inset_right: {}_f32, inset_bottom: {}_f32, inset_left: {}_f32, border_radius: {} }})",
340
                    inset_top, inset_right, inset_bottom, inset_left, br
341
                )
342
            }
343
            CssShape::Path(ShapePath { data }) => {
344
                format!("CssShape::Path(ShapePath {{ data: AzString::from_const_str(\"{}\") }})", data.as_str())
345
            }
346
        }
347
    }
348
}
349

            
350
impl_option!(
351
    CssShape,
352
    OptionCssShape,
353
    copy = false,
354
    [Debug, Clone, PartialEq]
355
);
356