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
23#[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 ShapeOutside::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 (ShapeOutside::None, ShapeOutside::None) => core::cmp::Ordering::Equal,
51 (ShapeOutside::None, ShapeOutside::Shape(_)) => core::cmp::Ordering::Less,
52 (ShapeOutside::Shape(_), ShapeOutside::None) => core::cmp::Ordering::Greater,
53 (ShapeOutside::Shape(a), ShapeOutside::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
68#[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 ShapeInside::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 (ShapeInside::None, ShapeInside::None) => core::cmp::Ordering::Equal,
96 (ShapeInside::None, ShapeInside::Shape(_)) => core::cmp::Ordering::Less,
97 (ShapeInside::Shape(_), ShapeInside::None) => core::cmp::Ordering::Greater,
98 (ShapeInside::Shape(a), ShapeInside::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
113#[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 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
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::format_rust_code::FormatAsRustCode for ShapeOutside {
202 fn format_as_rust_code(&self, _tabs: usize) -> String {
203 match self {
204 ShapeOutside::None => String::from("ShapeOutside::None"),
205 ShapeOutside::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::format_rust_code::FormatAsRustCode for ShapeInside {
216 fn format_as_rust_code(&self, _tabs: usize) -> String {
217 match self {
218 ShapeInside::None => String::from("ShapeInside::None"),
219 ShapeInside::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::format_rust_code::FormatAsRustCode for ClipPath {
230 fn format_as_rust_code(&self, _tabs: usize) -> String {
231 match self {
232 ClipPath::None => String::from("ClipPath::None"),
233 ClipPath::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::format_rust_code::FormatAsRustCode for ShapeMargin {
244 fn format_as_rust_code(&self, _tabs: usize) -> String {
245 format!(
246 "ShapeMargin {{ inner: {} }}",
247 crate::format_rust_code::format_pixel_value(&self.inner)
248 )
249 }
250}
251
252impl crate::format_rust_code::FormatAsRustCode for ShapeImageThreshold {
253 fn format_as_rust_code(&self, _tabs: usize) -> String {
254 format!(
255 "ShapeImageThreshold {{ inner: {} }}",
256 crate::format_rust_code::format_float_value(&self.inner)
257 )
258 }
259}
260
261#[cfg(feature = "parser")]
263pub mod parser {
264 use core::num::ParseFloatError;
265
266 use super::*;
267 use crate::shape_parser::{parse_shape, ShapeParseError};
268
269 pub fn parse_shape_outside(input: &str) -> Result<ShapeOutside, ShapeParseError> {
271 let trimmed = input.trim();
272 if trimmed == "none" {
273 Ok(ShapeOutside::None)
274 } else {
275 let shape = parse_shape(trimmed)?;
276 Ok(ShapeOutside::Shape(shape))
277 }
278 }
279
280 pub fn parse_shape_inside(input: &str) -> Result<ShapeInside, ShapeParseError> {
282 let trimmed = input.trim();
283 if trimmed == "none" {
284 Ok(ShapeInside::None)
285 } else {
286 let shape = parse_shape(trimmed)?;
287 Ok(ShapeInside::Shape(shape))
288 }
289 }
290
291 pub fn parse_clip_path(input: &str) -> Result<ClipPath, ShapeParseError> {
293 let trimmed = input.trim();
294 if trimmed == "none" {
295 Ok(ClipPath::None)
296 } else {
297 let shape = parse_shape(trimmed)?;
298 Ok(ClipPath::Shape(shape))
299 }
300 }
301
302 pub fn parse_shape_margin(input: &str) -> Result<ShapeMargin, CssPixelValueParseError<'_>> {
304 Ok(ShapeMargin {
305 inner: parse_pixel_value(input)?,
306 })
307 }
308
309 pub fn parse_shape_image_threshold(
311 input: &str,
312 ) -> Result<ShapeImageThreshold, ParseFloatError> {
313 let val = parse_float_value(input)?;
314 let clamped = val.get().clamp(0.0, 1.0);
316 Ok(ShapeImageThreshold {
317 inner: FloatValue::new(clamped),
318 })
319 }
320}
321
322#[cfg(feature = "parser")]
323pub use parser::*;
324
325#[cfg(all(test, feature = "parser"))]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_parse_shape_properties() {
331 assert!(matches!(
333 parse_shape_outside("none").unwrap(),
334 ShapeOutside::None
335 ));
336 assert!(matches!(
337 parse_shape_outside("circle(50px)").unwrap(),
338 ShapeOutside::Shape(_)
339 ));
340
341 assert!(matches!(
343 parse_shape_inside("none").unwrap(),
344 ShapeInside::None
345 ));
346 assert!(matches!(
347 parse_shape_inside("circle(100px at 50px 50px)").unwrap(),
348 ShapeInside::Shape(_)
349 ));
350
351 assert!(matches!(parse_clip_path("none").unwrap(), ClipPath::None));
353 assert!(matches!(
354 parse_clip_path("polygon(0 0, 100px 0, 100px 100px, 0 100px)").unwrap(),
355 ClipPath::Shape(_)
356 ));
357
358 assert_eq!(
360 parse_shape_margin("10px").unwrap().inner,
361 PixelValue::px(10.0)
362 );
363 assert_eq!(parse_shape_image_threshold("0.5").unwrap().inner.get(), 0.5);
364 }
365}