1
//! CSS properties for border radius (`border-top-left-radius`,
2
//! `border-top-right-radius`, `border-bottom-left-radius`,
3
//! `border-bottom-right-radius`) and the `border-radius` shorthand parser.
4

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

            
8
use crate::{
9
    props::{
10
        basic::pixel::{
11
            parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
12
        },
13
        macros::PixelValueTaker,
14
    },
15
};
16

            
17
// --- Property Struct Definitions ---
18

            
19
macro_rules! define_border_radius_property {
20
    ($struct_name:ident) => {
21
        #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22
        #[repr(C)]
23
        pub struct $struct_name {
24
            pub inner: PixelValue,
25
        }
26

            
27
        impl ::core::fmt::Debug for $struct_name {
28
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
29
                write!(f, "{}", self.inner)
30
            }
31
        }
32

            
33
        impl PixelValueTaker for $struct_name {
34
            fn from_pixel_value(inner: PixelValue) -> Self {
35
                Self { inner }
36
            }
37
        }
38

            
39
        impl_pixel_value!($struct_name);
40
    };
41
}
42

            
43
/// CSS `border-top-left-radius` property value.
44
define_border_radius_property!(StyleBorderTopLeftRadius);
45
/// CSS `border-top-right-radius` property value.
46
define_border_radius_property!(StyleBorderTopRightRadius);
47
/// CSS `border-bottom-left-radius` property value.
48
define_border_radius_property!(StyleBorderBottomLeftRadius);
49
/// CSS `border-bottom-right-radius` property value.
50
define_border_radius_property!(StyleBorderBottomRightRadius);
51

            
52
// --- Parser-only Struct ---
53

            
54
/// A temporary struct used only during the parsing of the `border-radius` shorthand property.
55
#[cfg(feature = "parser")]
56
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57
pub struct StyleBorderRadius {
58
    pub top_left: PixelValue,
59
    pub top_right: PixelValue,
60
    pub bottom_left: PixelValue,
61
    pub bottom_right: PixelValue,
62
}
63

            
64
// --- Error Types ---
65

            
66
/// Error for the shorthand `border-radius` property.
67
#[derive(Clone, PartialEq)]
68
pub enum CssBorderRadiusParseError<'a> {
69
    /// Too many values were provided (max is 4).
70
    TooManyValues(&'a str),
71
    /// An underlying pixel value could not be parsed.
72
    PixelValue(CssPixelValueParseError<'a>),
73
}
74

            
75
impl_debug_as_display!(CssBorderRadiusParseError<'a>);
76
impl_display! { CssBorderRadiusParseError<'a>, {
77
    TooManyValues(val) => format!("Too many values for border-radius: \"{}\"", val),
78
    PixelValue(e) => format!("{}", e),
79
}}
80
impl_from!(
81
    CssPixelValueParseError<'a>,
82
    CssBorderRadiusParseError::PixelValue
83
);
84

            
85
/// Owned version of `CssBorderRadiusParseError`.
86
#[derive(Debug, Clone, PartialEq)]
87
#[repr(C, u8)]
88
pub enum CssBorderRadiusParseErrorOwned {
89
    TooManyValues(AzString),
90
    PixelValue(CssPixelValueParseErrorOwned),
91
}
92

            
93
/// Newtype wrapper around `CssBorderRadiusParseErrorOwned` for the `border-radius` shorthand.
94
#[derive(Debug, Clone, PartialEq)]
95
#[repr(C)]
96
pub struct CssStyleBorderRadiusParseErrorOwned {
97
    pub inner: CssBorderRadiusParseErrorOwned,
98
}
99

            
100
impl From<CssBorderRadiusParseErrorOwned> for CssStyleBorderRadiusParseErrorOwned {
101
    fn from(v: CssBorderRadiusParseErrorOwned) -> Self {
102
        Self { inner: v }
103
    }
104
}
105

            
106
impl<'a> CssBorderRadiusParseError<'a> {
107
    pub fn to_contained(&self) -> CssBorderRadiusParseErrorOwned {
108
        match self {
109
            CssBorderRadiusParseError::TooManyValues(s) => {
110
                CssBorderRadiusParseErrorOwned::TooManyValues(s.to_string().into())
111
            }
112
            CssBorderRadiusParseError::PixelValue(e) => {
113
                CssBorderRadiusParseErrorOwned::PixelValue(e.to_contained())
114
            }
115
        }
116
    }
117
}
118

            
119
impl CssBorderRadiusParseErrorOwned {
120
    pub fn to_shared<'a>(&'a self) -> CssBorderRadiusParseError<'a> {
121
        match self {
122
            CssBorderRadiusParseErrorOwned::TooManyValues(s) => {
123
                CssBorderRadiusParseError::TooManyValues(s)
124
            }
125
            CssBorderRadiusParseErrorOwned::PixelValue(e) => {
126
                CssBorderRadiusParseError::PixelValue(e.to_shared())
127
            }
128
        }
129
    }
130
}
131

            
132
/// Macro to generate error types for individual radius properties.
133
macro_rules! define_border_radius_parse_error {
134
    ($error_name:ident, $error_name_owned:ident) => {
135
        #[derive(Clone, PartialEq)]
136
        pub enum $error_name<'a> {
137
            PixelValue(CssPixelValueParseError<'a>),
138
        }
139

            
140
        impl_debug_as_display!($error_name<'a>);
141
        impl_display! { $error_name<'a>, {
142
            PixelValue(e) => format!("{}", e),
143
        }}
144

            
145
        impl_from!(CssPixelValueParseError<'a>, $error_name::PixelValue);
146

            
147
        #[derive(Debug, Clone, PartialEq)]
148
        #[repr(C, u8)]
149
        pub enum $error_name_owned {
150
            PixelValue(CssPixelValueParseErrorOwned),
151
        }
152

            
153
        impl<'a> $error_name<'a> {
154
            pub fn to_contained(&self) -> $error_name_owned {
155
                match self {
156
                    $error_name::PixelValue(e) => $error_name_owned::PixelValue(e.to_contained()),
157
                }
158
            }
159
        }
160

            
161
        impl $error_name_owned {
162
            pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
163
                match self {
164
                    $error_name_owned::PixelValue(e) => $error_name::PixelValue(e.to_shared()),
165
                }
166
            }
167
        }
168
    };
169
}
170

            
171
define_border_radius_parse_error!(
172
    StyleBorderTopLeftRadiusParseError,
173
    StyleBorderTopLeftRadiusParseErrorOwned
174
);
175
define_border_radius_parse_error!(
176
    StyleBorderTopRightRadiusParseError,
177
    StyleBorderTopRightRadiusParseErrorOwned
178
);
179
define_border_radius_parse_error!(
180
    StyleBorderBottomLeftRadiusParseError,
181
    StyleBorderBottomLeftRadiusParseErrorOwned
182
);
183
define_border_radius_parse_error!(
184
    StyleBorderBottomRightRadiusParseError,
185
    StyleBorderBottomRightRadiusParseErrorOwned
186
);
187

            
188
// --- Parsing Functions ---
189

            
190
/// Parse the CSS `border-radius` shorthand into individual corner values.
191
#[cfg(feature = "parser")]
192
8
pub fn parse_style_border_radius<'a>(
193
8
    input: &'a str,
194
8
) -> Result<StyleBorderRadius, CssBorderRadiusParseError<'a>> {
195
8
    let components: Vec<_> = input.split_whitespace().collect();
196
8
    let mut values = Vec::with_capacity(components.len());
197
19
    for comp in components.iter() {
198
19
        values.push(parse_pixel_value(comp)?);
199
    }
200

            
201
7
    match values.len() {
202
1
        1 => Ok(StyleBorderRadius {
203
1
            top_left: values[0],
204
1
            top_right: values[0],
205
1
            bottom_right: values[0],
206
1
            bottom_left: values[0],
207
1
        }),
208
2
        2 => Ok(StyleBorderRadius {
209
2
            top_left: values[0],
210
2
            top_right: values[1],
211
2
            bottom_right: values[0],
212
2
            bottom_left: values[1],
213
2
        }),
214
1
        3 => Ok(StyleBorderRadius {
215
1
            top_left: values[0],
216
1
            top_right: values[1],
217
1
            bottom_right: values[2],
218
1
            bottom_left: values[1],
219
1
        }),
220
1
        4 => Ok(StyleBorderRadius {
221
1
            top_left: values[0],
222
1
            top_right: values[1],
223
1
            bottom_right: values[2],
224
1
            bottom_left: values[3],
225
1
        }),
226
2
        _ => Err(CssBorderRadiusParseError::TooManyValues(input)),
227
    }
228
8
}
229

            
230
/// Parse the CSS `border-top-left-radius` longhand property.
231
#[cfg(feature = "parser")]
232
1
pub fn parse_style_border_top_left_radius<'a>(
233
1
    input: &'a str,
234
1
) -> Result<StyleBorderTopLeftRadius, StyleBorderTopLeftRadiusParseError<'a>> {
235
1
    let pixel_value = parse_pixel_value(input)?;
236
1
    Ok(StyleBorderTopLeftRadius { inner: pixel_value })
237
1
}
238

            
239
/// Parse the CSS `border-top-right-radius` longhand property.
240
#[cfg(feature = "parser")]
241
pub fn parse_style_border_top_right_radius<'a>(
242
    input: &'a str,
243
) -> Result<StyleBorderTopRightRadius, StyleBorderTopRightRadiusParseError<'a>> {
244
    let pixel_value = parse_pixel_value(input)?;
245
    Ok(StyleBorderTopRightRadius { inner: pixel_value })
246
}
247

            
248
/// Parse the CSS `border-bottom-left-radius` longhand property.
249
#[cfg(feature = "parser")]
250
pub fn parse_style_border_bottom_left_radius<'a>(
251
    input: &'a str,
252
) -> Result<StyleBorderBottomLeftRadius, StyleBorderBottomLeftRadiusParseError<'a>> {
253
    let pixel_value = parse_pixel_value(input)?;
254
    Ok(StyleBorderBottomLeftRadius { inner: pixel_value })
255
}
256

            
257
/// Parse the CSS `border-bottom-right-radius` longhand property.
258
#[cfg(feature = "parser")]
259
pub fn parse_style_border_bottom_right_radius<'a>(
260
    input: &'a str,
261
) -> Result<StyleBorderBottomRightRadius, StyleBorderBottomRightRadiusParseError<'a>> {
262
    let pixel_value = parse_pixel_value(input)?;
263
    Ok(StyleBorderBottomRightRadius { inner: pixel_value })
264
}
265

            
266
#[cfg(all(test, feature = "parser"))]
267
mod tests {
268
    use super::*;
269

            
270
    #[test]
271
1
    fn test_parse_border_radius_shorthand() {
272
        // One value
273
1
        let result = parse_style_border_radius("10px").unwrap();
274
1
        assert_eq!(result.top_left, PixelValue::px(10.0));
275
1
        assert_eq!(result.top_right, PixelValue::px(10.0));
276
1
        assert_eq!(result.bottom_right, PixelValue::px(10.0));
277
1
        assert_eq!(result.bottom_left, PixelValue::px(10.0));
278

            
279
        // Two values
280
1
        let result = parse_style_border_radius("10px 5%").unwrap();
281
1
        assert_eq!(result.top_left, PixelValue::px(10.0));
282
1
        assert_eq!(result.top_right, PixelValue::percent(5.0));
283
1
        assert_eq!(result.bottom_right, PixelValue::px(10.0));
284
1
        assert_eq!(result.bottom_left, PixelValue::percent(5.0));
285

            
286
        // Three values
287
1
        let result = parse_style_border_radius("2px 4px 8px").unwrap();
288
1
        assert_eq!(result.top_left, PixelValue::px(2.0));
289
1
        assert_eq!(result.top_right, PixelValue::px(4.0));
290
1
        assert_eq!(result.bottom_right, PixelValue::px(8.0));
291
1
        assert_eq!(result.bottom_left, PixelValue::px(4.0));
292

            
293
        // Four values
294
1
        let result = parse_style_border_radius("1px 0 3px 4px").unwrap();
295
1
        assert_eq!(result.top_left, PixelValue::px(1.0));
296
1
        assert_eq!(result.top_right, PixelValue::px(0.0));
297
1
        assert_eq!(result.bottom_right, PixelValue::px(3.0));
298
1
        assert_eq!(result.bottom_left, PixelValue::px(4.0));
299

            
300
        // Weird whitespace
301
1
        let result = parse_style_border_radius("  1em   2em  ").unwrap();
302
1
        assert_eq!(result.top_left, PixelValue::em(1.0));
303
1
        assert_eq!(result.top_right, PixelValue::em(2.0));
304
1
    }
305

            
306
    #[test]
307
1
    fn test_parse_border_radius_shorthand_errors() {
308
1
        assert!(parse_style_border_radius("").is_err());
309
1
        assert!(parse_style_border_radius("1px 2px 3px 4px 5px").is_err());
310
1
        assert!(parse_style_border_radius("1px bad 3px").is_err());
311
1
    }
312

            
313
    #[test]
314
1
    fn test_parse_longhand_radius() {
315
1
        let result = parse_style_border_top_left_radius("25%").unwrap();
316
1
        assert_eq!(result.inner, PixelValue::percent(25.0));
317
1
    }
318
}