1use alloc::string::{String, ToString};
4
5use crate::{
6 props::{
7 basic::{
8 length::{parse_float_value, FloatValue},
9 pixel::{
10 parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
11 PixelValue,
12 },
13 },
14 formatter::PrintAsCssValue,
15 },
16 shape::CssShape,
17};
18
19#[derive(Debug, Clone, PartialEq)]
21#[repr(C, u8)]
22pub enum ShapeOutside {
23 None,
24 Shape(CssShape),
25}
26
27impl Eq for ShapeOutside {}
28impl core::hash::Hash for ShapeOutside {
29 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
30 core::mem::discriminant(self).hash(state);
31 if let ShapeOutside::Shape(s) = self {
32 s.hash(state);
33 }
34 }
35}
36impl PartialOrd for ShapeOutside {
37 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
38 Some(self.cmp(other))
39 }
40}
41impl Ord for ShapeOutside {
42 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
43 match (self, other) {
44 (ShapeOutside::None, ShapeOutside::None) => core::cmp::Ordering::Equal,
45 (ShapeOutside::None, ShapeOutside::Shape(_)) => core::cmp::Ordering::Less,
46 (ShapeOutside::Shape(_), ShapeOutside::None) => core::cmp::Ordering::Greater,
47 (ShapeOutside::Shape(a), ShapeOutside::Shape(b)) => a.cmp(b),
48 }
49 }
50}
51
52impl Default for ShapeOutside {
53 fn default() -> Self {
54 Self::None
55 }
56}
57
58impl PrintAsCssValue for ShapeOutside {
59 fn print_as_css_value(&self) -> String {
60 match self {
61 Self::None => "none".to_string(),
62 Self::Shape(shape) => format!("{:?}", shape), }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq)]
69#[repr(C, u8)]
70pub enum ShapeInside {
71 None,
72 Shape(CssShape),
73}
74
75impl Eq for ShapeInside {}
76impl core::hash::Hash for ShapeInside {
77 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
78 core::mem::discriminant(self).hash(state);
79 if let ShapeInside::Shape(s) = self {
80 s.hash(state);
81 }
82 }
83}
84impl PartialOrd for ShapeInside {
85 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
86 Some(self.cmp(other))
87 }
88}
89impl Ord for ShapeInside {
90 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
91 match (self, other) {
92 (ShapeInside::None, ShapeInside::None) => core::cmp::Ordering::Equal,
93 (ShapeInside::None, ShapeInside::Shape(_)) => core::cmp::Ordering::Less,
94 (ShapeInside::Shape(_), ShapeInside::None) => core::cmp::Ordering::Greater,
95 (ShapeInside::Shape(a), ShapeInside::Shape(b)) => a.cmp(b),
96 }
97 }
98}
99
100impl Default for ShapeInside {
101 fn default() -> Self {
102 Self::None
103 }
104}
105
106impl PrintAsCssValue for ShapeInside {
107 fn print_as_css_value(&self) -> String {
108 match self {
109 Self::None => "none".to_string(),
110 Self::Shape(shape) => format!("{:?}", shape), }
112 }
113}
114
115#[derive(Debug, Clone, PartialEq)]
117#[repr(C, u8)]
118pub enum ClipPath {
119 None,
120 Shape(CssShape),
121}
122
123impl Eq for ClipPath {}
124impl core::hash::Hash for ClipPath {
125 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
126 core::mem::discriminant(self).hash(state);
127 if let ClipPath::Shape(s) = self {
128 s.hash(state);
129 }
130 }
131}
132impl PartialOrd for ClipPath {
133 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
134 Some(self.cmp(other))
135 }
136}
137impl Ord for ClipPath {
138 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
139 match (self, other) {
140 (ClipPath::None, ClipPath::None) => core::cmp::Ordering::Equal,
141 (ClipPath::None, ClipPath::Shape(_)) => core::cmp::Ordering::Less,
142 (ClipPath::Shape(_), ClipPath::None) => core::cmp::Ordering::Greater,
143 (ClipPath::Shape(a), ClipPath::Shape(b)) => a.cmp(b),
144 }
145 }
146}
147
148impl Default for ClipPath {
149 fn default() -> Self {
150 Self::None
151 }
152}
153
154impl PrintAsCssValue for ClipPath {
155 fn print_as_css_value(&self) -> String {
156 match self {
157 Self::None => "none".to_string(),
158 Self::Shape(shape) => format!("{:?}", shape), }
160 }
161}
162
163#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
164#[repr(C)]
165pub struct ShapeMargin {
166 pub inner: PixelValue,
167}
168
169impl Default for ShapeMargin {
170 fn default() -> Self {
171 Self {
172 inner: PixelValue::zero(),
173 }
174 }
175}
176
177impl PrintAsCssValue for ShapeMargin {
178 fn print_as_css_value(&self) -> String {
179 self.inner.print_as_css_value()
180 }
181}
182
183#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
184#[repr(C)]
185pub struct ShapeImageThreshold {
186 pub inner: FloatValue,
187}
188
189impl Default for ShapeImageThreshold {
190 fn default() -> Self {
191 Self {
192 inner: FloatValue::const_new(0),
193 }
194 }
195}
196
197impl PrintAsCssValue for ShapeImageThreshold {
198 fn print_as_css_value(&self) -> String {
199 self.inner.to_string()
200 }
201}
202
203impl crate::format_rust_code::FormatAsRustCode for ShapeOutside {
205 fn format_as_rust_code(&self, _tabs: usize) -> String {
206 match self {
207 ShapeOutside::None => String::from("ShapeOutside::None"),
208 ShapeOutside::Shape(_s) => String::from("ShapeOutside::Shape(/* ... */)"), }
210 }
211}
212
213impl crate::format_rust_code::FormatAsRustCode for ShapeInside {
214 fn format_as_rust_code(&self, _tabs: usize) -> String {
215 match self {
216 ShapeInside::None => String::from("ShapeInside::None"),
217 ShapeInside::Shape(_s) => String::from("ShapeInside::Shape(/* ... */)"), }
219 }
220}
221
222impl crate::format_rust_code::FormatAsRustCode for ClipPath {
223 fn format_as_rust_code(&self, _tabs: usize) -> String {
224 match self {
225 ClipPath::None => String::from("ClipPath::None"),
226 ClipPath::Shape(_s) => String::from("ClipPath::Shape(/* ... */)"), }
228 }
229}
230
231impl crate::format_rust_code::FormatAsRustCode for ShapeMargin {
232 fn format_as_rust_code(&self, _tabs: usize) -> String {
233 format!(
234 "ShapeMargin {{ inner: {} }}",
235 crate::format_rust_code::format_pixel_value(&self.inner)
236 )
237 }
238}
239
240impl crate::format_rust_code::FormatAsRustCode for ShapeImageThreshold {
241 fn format_as_rust_code(&self, _tabs: usize) -> String {
242 format!(
243 "ShapeImageThreshold {{ inner: {} }}",
244 crate::format_rust_code::format_float_value(&self.inner)
245 )
246 }
247}
248
249#[cfg(feature = "parser")]
251mod parser {
252 use core::num::ParseFloatError;
253
254 use super::*;
255 use crate::shape_parser::{parse_shape, ShapeParseError};
256
257 pub fn parse_shape_outside(input: &str) -> Result<ShapeOutside, ShapeParseError> {
259 let trimmed = input.trim();
260 if trimmed == "none" {
261 Ok(ShapeOutside::None)
262 } else {
263 let shape = parse_shape(trimmed)?;
264 Ok(ShapeOutside::Shape(shape))
265 }
266 }
267
268 pub fn parse_shape_inside(input: &str) -> Result<ShapeInside, ShapeParseError> {
270 let trimmed = input.trim();
271 if trimmed == "none" {
272 Ok(ShapeInside::None)
273 } else {
274 let shape = parse_shape(trimmed)?;
275 Ok(ShapeInside::Shape(shape))
276 }
277 }
278
279 pub fn parse_clip_path(input: &str) -> Result<ClipPath, ShapeParseError> {
281 let trimmed = input.trim();
282 if trimmed == "none" {
283 Ok(ClipPath::None)
284 } else {
285 let shape = parse_shape(trimmed)?;
286 Ok(ClipPath::Shape(shape))
287 }
288 }
289
290 pub fn parse_shape_margin(input: &str) -> Result<ShapeMargin, CssPixelValueParseError> {
292 Ok(ShapeMargin {
293 inner: parse_pixel_value(input)?,
294 })
295 }
296
297 pub fn parse_shape_image_threshold(
298 input: &str,
299 ) -> Result<ShapeImageThreshold, ParseFloatError> {
300 let val = parse_float_value(input)?;
301 let clamped = val.get().max(0.0).min(1.0);
303 Ok(ShapeImageThreshold {
304 inner: FloatValue::new(clamped),
305 })
306 }
307}
308
309#[cfg(feature = "parser")]
310pub use parser::*;
311
312#[cfg(all(test, feature = "parser"))]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_parse_shape_properties() {
318 assert!(matches!(
320 parse_shape_outside("none").unwrap(),
321 ShapeOutside::None
322 ));
323 assert!(matches!(
324 parse_shape_outside("circle(50px)").unwrap(),
325 ShapeOutside::Shape(_)
326 ));
327
328 assert!(matches!(
330 parse_shape_inside("none").unwrap(),
331 ShapeInside::None
332 ));
333 assert!(matches!(
334 parse_shape_inside("circle(100px at 50px 50px)").unwrap(),
335 ShapeInside::Shape(_)
336 ));
337
338 assert!(matches!(parse_clip_path("none").unwrap(), ClipPath::None));
340 assert!(matches!(
341 parse_clip_path("polygon(0 0, 100px 0, 100px 100px, 0 100px)").unwrap(),
342 ClipPath::Shape(_)
343 ));
344
345 assert_eq!(
347 parse_shape_margin("10px").unwrap().inner,
348 PixelValue::px(10.0)
349 );
350 assert_eq!(parse_shape_image_threshold("0.5").unwrap().inner.get(), 0.5);
351 }
352}