1use alloc::string::{String, ToString};
8
9use crate::{
10 props::{
11 basic::{
12 length::{parse_float_value, FloatValue},
13 pixel::{
14 parse_pixel_value, CssPixelValueParseError,
15 PixelValue,
16 },
17 },
18 formatter::PrintAsCssValue,
19 },
20 shape::CssShape,
21};
22#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq)]
25#[repr(C, u8)]
26#[derive(Default)]
27pub enum ShapeOutside {
28 #[default]
29 None,
30 Shape(CssShape),
31}
32
33impl Eq for ShapeOutside {}
34impl core::hash::Hash for ShapeOutside {
35 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
36 core::mem::discriminant(self).hash(state);
37 if let Self::Shape(s) = self {
38 s.hash(state);
39 }
40 }
41}
42impl PartialOrd for ShapeOutside {
43 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
44 Some(self.cmp(other))
45 }
46}
47impl Ord for ShapeOutside {
48 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
49 match (self, other) {
50 (Self::None, Self::None) => core::cmp::Ordering::Equal,
51 (Self::None, Self::Shape(_)) => core::cmp::Ordering::Less,
52 (Self::Shape(_), Self::None) => core::cmp::Ordering::Greater,
53 (Self::Shape(a), Self::Shape(b)) => a.cmp(b),
54 }
55 }
56}
57
58
59impl PrintAsCssValue for ShapeOutside {
60 fn print_as_css_value(&self) -> String {
61 match self {
62 Self::None => "none".to_string(),
63 Self::Shape(shape) => shape.print_as_css_value(),
64 }
65 }
66}
67#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq)]
70#[repr(C, u8)]
71#[derive(Default)]
72pub enum ShapeInside {
73 #[default]
74 None,
75 Shape(CssShape),
76}
77
78impl Eq for ShapeInside {}
79impl core::hash::Hash for ShapeInside {
80 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
81 core::mem::discriminant(self).hash(state);
82 if let Self::Shape(s) = self {
83 s.hash(state);
84 }
85 }
86}
87impl PartialOrd for ShapeInside {
88 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
89 Some(self.cmp(other))
90 }
91}
92impl Ord for ShapeInside {
93 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
94 match (self, other) {
95 (Self::None, Self::None) => core::cmp::Ordering::Equal,
96 (Self::None, Self::Shape(_)) => core::cmp::Ordering::Less,
97 (Self::Shape(_), Self::None) => core::cmp::Ordering::Greater,
98 (Self::Shape(a), Self::Shape(b)) => a.cmp(b),
99 }
100 }
101}
102
103
104impl PrintAsCssValue for ShapeInside {
105 fn print_as_css_value(&self) -> String {
106 match self {
107 Self::None => "none".to_string(),
108 Self::Shape(shape) => shape.print_as_css_value(),
109 }
110 }
111}
112#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq)]
115#[repr(C, u8)]
116#[derive(Default)]
117pub enum ClipPath {
118 #[default]
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 Self::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 (Self::None, Self::None) => core::cmp::Ordering::Equal,
141 (Self::None, Self::Shape(_)) => core::cmp::Ordering::Less,
142 (Self::Shape(_), Self::None) => core::cmp::Ordering::Greater,
143 (Self::Shape(a), Self::Shape(b)) => a.cmp(b),
144 }
145 }
146}
147
148
149impl PrintAsCssValue for ClipPath {
150 fn print_as_css_value(&self) -> String {
151 match self {
152 Self::None => "none".to_string(),
153 Self::Shape(shape) => shape.print_as_css_value(),
154 }
155 }
156}
157
158#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
160#[repr(C)]
161pub struct ShapeMargin {
162 pub inner: PixelValue,
163}
164
165impl Default for ShapeMargin {
166 fn default() -> Self {
167 Self {
168 inner: PixelValue::zero(),
169 }
170 }
171}
172
173impl PrintAsCssValue for ShapeMargin {
174 fn print_as_css_value(&self) -> String {
175 self.inner.print_as_css_value()
176 }
177}
178
179#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
181#[repr(C)]
182pub struct ShapeImageThreshold {
183 pub inner: FloatValue,
184}
185
186impl Default for ShapeImageThreshold {
187 fn default() -> Self {
188 Self {
189 inner: FloatValue::const_new(0),
190 }
191 }
192}
193
194impl PrintAsCssValue for ShapeImageThreshold {
195 fn print_as_css_value(&self) -> String {
196 self.inner.to_string()
197 }
198}
199
200impl crate::codegen::format::FormatAsRustCode for ShapeOutside {
202 fn format_as_rust_code(&self, _tabs: usize) -> String {
203 match self {
204 Self::None => String::from("ShapeOutside::None"),
205 Self::Shape(s) => {
206 let mut r = String::from("ShapeOutside::Shape(");
207 r.push_str(&s.format_as_rust_code());
208 r.push(')');
209 r
210 }
211 }
212 }
213}
214
215impl crate::codegen::format::FormatAsRustCode for ShapeInside {
216 fn format_as_rust_code(&self, _tabs: usize) -> String {
217 match self {
218 Self::None => String::from("ShapeInside::None"),
219 Self::Shape(s) => {
220 let mut r = String::from("ShapeInside::Shape(");
221 r.push_str(&s.format_as_rust_code());
222 r.push(')');
223 r
224 }
225 }
226 }
227}
228
229impl crate::codegen::format::FormatAsRustCode for ClipPath {
230 fn format_as_rust_code(&self, _tabs: usize) -> String {
231 match self {
232 Self::None => String::from("ClipPath::None"),
233 Self::Shape(s) => {
234 let mut r = String::from("ClipPath::Shape(");
235 r.push_str(&s.format_as_rust_code());
236 r.push(')');
237 r
238 }
239 }
240 }
241}
242
243impl crate::codegen::format::FormatAsRustCode for ShapeMargin {
244 fn format_as_rust_code(&self, _tabs: usize) -> String {
245 format!(
246 "ShapeMargin {{ inner: {} }}",
247 crate::codegen::format::format_pixel_value(&self.inner)
248 )
249 }
250}
251
252impl crate::codegen::format::FormatAsRustCode for ShapeImageThreshold {
253 fn format_as_rust_code(&self, _tabs: usize) -> String {
254 format!(
255 "ShapeImageThreshold {{ inner: {} }}",
256 crate::codegen::format::format_float_value(&self.inner)
257 )
258 }
259}
260
261#[cfg(feature = "parser")]
263pub mod parser {
264 use core::num::ParseFloatError;
265
266 #[allow(clippy::wildcard_imports)] use super::*;
268 use crate::shape_parser::{parse_shape, ShapeParseError};
269
270 pub fn parse_shape_outside(input: &str) -> Result<ShapeOutside, ShapeParseError> {
275 let trimmed = input.trim();
276 if trimmed == "none" {
277 Ok(ShapeOutside::None)
278 } else {
279 let shape = parse_shape(trimmed)?;
280 Ok(ShapeOutside::Shape(shape))
281 }
282 }
283
284 pub fn parse_shape_inside(input: &str) -> Result<ShapeInside, ShapeParseError> {
289 let trimmed = input.trim();
290 if trimmed == "none" {
291 Ok(ShapeInside::None)
292 } else {
293 let shape = parse_shape(trimmed)?;
294 Ok(ShapeInside::Shape(shape))
295 }
296 }
297
298 pub fn parse_clip_path(input: &str) -> Result<ClipPath, ShapeParseError> {
303 let trimmed = input.trim();
304 if trimmed == "none" {
305 Ok(ClipPath::None)
306 } else {
307 let shape = parse_shape(trimmed)?;
308 Ok(ClipPath::Shape(shape))
309 }
310 }
311
312 pub fn parse_shape_margin(input: &str) -> Result<ShapeMargin, CssPixelValueParseError<'_>> {
317 Ok(ShapeMargin {
318 inner: parse_pixel_value(input)?,
319 })
320 }
321
322 pub fn parse_shape_image_threshold(
327 input: &str,
328 ) -> Result<ShapeImageThreshold, ParseFloatError> {
329 let val = parse_float_value(input)?;
330 let clamped = val.get().clamp(0.0, 1.0);
332 Ok(ShapeImageThreshold {
333 inner: FloatValue::new(clamped),
334 })
335 }
336}
337
338#[cfg(feature = "parser")]
339pub use parser::*;
340
341#[cfg(all(test, feature = "parser"))]
342mod tests {
343 #![allow(clippy::float_cmp)]
345 use super::*;
346
347 #[test]
348 fn test_parse_shape_properties() {
349 assert!(matches!(
351 parse_shape_outside("none").unwrap(),
352 ShapeOutside::None
353 ));
354 assert!(matches!(
355 parse_shape_outside("circle(50px)").unwrap(),
356 ShapeOutside::Shape(_)
357 ));
358
359 assert!(matches!(
361 parse_shape_inside("none").unwrap(),
362 ShapeInside::None
363 ));
364 assert!(matches!(
365 parse_shape_inside("circle(100px at 50px 50px)").unwrap(),
366 ShapeInside::Shape(_)
367 ));
368
369 assert!(matches!(parse_clip_path("none").unwrap(), ClipPath::None));
371 assert!(matches!(
372 parse_clip_path("polygon(0 0, 100px 0, 100px 100px, 0 100px)").unwrap(),
373 ClipPath::Shape(_)
374 ));
375
376 assert_eq!(
378 parse_shape_margin("10px").unwrap().inner,
379 PixelValue::px(10.0)
380 );
381 assert_eq!(parse_shape_image_threshold("0.5").unwrap().inner.get(), 0.5);
382 }
383}