1use alloc::string::{String, ToString};
4
5use crate::props::{
6 basic::pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
7 formatter::PrintAsCssValue,
8 macros::PixelValueTaker,
9};
10
11macro_rules! define_dimension_property {
14 ($struct_name:ident, $default_fn:expr) => {
15 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16 #[repr(C)]
17 pub struct $struct_name {
18 pub inner: PixelValue,
19 }
20
21 impl Default for $struct_name {
22 fn default() -> Self {
23 $default_fn()
24 }
25 }
26
27 impl PixelValueTaker for $struct_name {
28 fn from_pixel_value(inner: PixelValue) -> Self {
29 Self { inner }
30 }
31 }
32
33 impl_pixel_value!($struct_name);
34
35 impl PrintAsCssValue for $struct_name {
36 fn print_as_css_value(&self) -> String {
37 self.inner.to_string()
38 }
39 }
40 };
41}
42
43#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45#[repr(C, u8)]
46pub enum LayoutWidth {
47 Auto, Px(PixelValue),
49 MinContent,
50 MaxContent,
51}
52
53impl Default for LayoutWidth {
54 fn default() -> Self {
55 LayoutWidth::Auto }
57}
58
59impl PixelValueTaker for LayoutWidth {
60 fn from_pixel_value(inner: PixelValue) -> Self {
61 LayoutWidth::Px(inner)
62 }
63}
64
65impl PrintAsCssValue for LayoutWidth {
66 fn print_as_css_value(&self) -> String {
67 match self {
68 LayoutWidth::Auto => "auto".to_string(),
69 LayoutWidth::Px(v) => v.to_string(),
70 LayoutWidth::MinContent => "min-content".to_string(),
71 LayoutWidth::MaxContent => "max-content".to_string(),
72 }
73 }
74}
75
76impl LayoutWidth {
77 pub fn px(value: f32) -> Self {
78 LayoutWidth::Px(PixelValue::px(value))
79 }
80
81 pub const fn const_px(value: isize) -> Self {
82 LayoutWidth::Px(PixelValue::const_px(value))
83 }
84
85 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
86 match (self, other) {
87 (LayoutWidth::Px(a), LayoutWidth::Px(b)) => LayoutWidth::Px(a.interpolate(b, t)),
88 (_, LayoutWidth::Px(b)) if t >= 0.5 => LayoutWidth::Px(*b),
91 (LayoutWidth::Px(a), _) if t < 0.5 => LayoutWidth::Px(*a),
92 (LayoutWidth::Auto, LayoutWidth::Auto) => LayoutWidth::Auto,
94 (a, _) if t < 0.5 => *a,
95 (_, b) => *b,
96 }
97 }
98}
99
100#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
102#[repr(C, u8)]
103pub enum LayoutHeight {
104 Auto, Px(PixelValue),
106 MinContent,
107 MaxContent,
108}
109
110impl Default for LayoutHeight {
111 fn default() -> Self {
112 LayoutHeight::Auto }
114}
115
116impl PixelValueTaker for LayoutHeight {
117 fn from_pixel_value(inner: PixelValue) -> Self {
118 LayoutHeight::Px(inner)
119 }
120}
121
122impl PrintAsCssValue for LayoutHeight {
123 fn print_as_css_value(&self) -> String {
124 match self {
125 LayoutHeight::Auto => "auto".to_string(),
126 LayoutHeight::Px(v) => v.to_string(),
127 LayoutHeight::MinContent => "min-content".to_string(),
128 LayoutHeight::MaxContent => "max-content".to_string(),
129 }
130 }
131}
132
133impl LayoutHeight {
134 pub fn px(value: f32) -> Self {
135 LayoutHeight::Px(PixelValue::px(value))
136 }
137
138 pub const fn const_px(value: isize) -> Self {
139 LayoutHeight::Px(PixelValue::const_px(value))
140 }
141
142 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
143 match (self, other) {
144 (LayoutHeight::Px(a), LayoutHeight::Px(b)) => LayoutHeight::Px(a.interpolate(b, t)),
145 (_, LayoutHeight::Px(b)) if t >= 0.5 => LayoutHeight::Px(*b),
148 (LayoutHeight::Px(a), _) if t < 0.5 => LayoutHeight::Px(*a),
149 (LayoutHeight::Auto, LayoutHeight::Auto) => LayoutHeight::Auto,
151 (a, _) if t < 0.5 => *a,
152 (_, b) => *b,
153 }
154 }
155}
156
157define_dimension_property!(LayoutMinWidth, || Self {
158 inner: PixelValue::zero()
159});
160define_dimension_property!(LayoutMinHeight, || Self {
161 inner: PixelValue::zero()
162});
163define_dimension_property!(LayoutMaxWidth, || Self {
164 inner: PixelValue::px(core::f32::MAX)
165});
166define_dimension_property!(LayoutMaxHeight, || Self {
167 inner: PixelValue::px(core::f32::MAX)
168});
169
170#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
172#[repr(C)]
173pub enum LayoutBoxSizing {
174 ContentBox,
175 BorderBox,
176}
177
178impl Default for LayoutBoxSizing {
179 fn default() -> Self {
180 LayoutBoxSizing::ContentBox
181 }
182}
183
184impl PrintAsCssValue for LayoutBoxSizing {
185 fn print_as_css_value(&self) -> String {
186 String::from(match self {
187 LayoutBoxSizing::ContentBox => "content-box",
188 LayoutBoxSizing::BorderBox => "border-box",
189 })
190 }
191}
192
193#[cfg(feature = "parser")]
196mod parser {
197
198 use alloc::string::ToString;
199
200 use super::*;
201 use crate::props::basic::pixel::parse_pixel_value;
202
203 macro_rules! define_pixel_dimension_parser {
204 ($fn_name:ident, $struct_name:ident, $error_name:ident, $error_owned_name:ident) => {
205 #[derive(Clone, PartialEq)]
206 pub enum $error_name<'a> {
207 PixelValue(CssPixelValueParseError<'a>),
208 }
209
210 impl_debug_as_display!($error_name<'a>);
211 impl_display! { $error_name<'a>, {
212 PixelValue(e) => format!("{}", e),
213 }}
214
215 impl_from! { CssPixelValueParseError<'a>, $error_name::PixelValue }
216
217 #[derive(Debug, Clone, PartialEq)]
218 pub enum $error_owned_name {
219 PixelValue(CssPixelValueParseErrorOwned),
220 }
221
222 impl<'a> $error_name<'a> {
223 pub fn to_contained(&self) -> $error_owned_name {
224 match self {
225 $error_name::PixelValue(e) => {
226 $error_owned_name::PixelValue(e.to_contained())
227 }
228 }
229 }
230 }
231
232 impl $error_owned_name {
233 pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
234 match self {
235 $error_owned_name::PixelValue(e) => $error_name::PixelValue(e.to_shared()),
236 }
237 }
238 }
239
240 pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
241 parse_pixel_value(input)
242 .map(|v| $struct_name { inner: v })
243 .map_err($error_name::PixelValue)
244 }
245 };
246 }
247
248 #[derive(Clone, PartialEq)]
251 pub enum LayoutWidthParseError<'a> {
252 PixelValue(CssPixelValueParseError<'a>),
253 InvalidKeyword(&'a str),
254 }
255
256 impl_debug_as_display!(LayoutWidthParseError<'a>);
257 impl_display! { LayoutWidthParseError<'a>, {
258 PixelValue(e) => format!("{}", e),
259 InvalidKeyword(k) => format!("Invalid width keyword: \"{}\"", k),
260 }}
261
262 impl_from! { CssPixelValueParseError<'a>, LayoutWidthParseError::PixelValue }
263
264 #[derive(Debug, Clone, PartialEq)]
265 pub enum LayoutWidthParseErrorOwned {
266 PixelValue(CssPixelValueParseErrorOwned),
267 InvalidKeyword(String),
268 }
269
270 impl<'a> LayoutWidthParseError<'a> {
271 pub fn to_contained(&self) -> LayoutWidthParseErrorOwned {
272 match self {
273 LayoutWidthParseError::PixelValue(e) => {
274 LayoutWidthParseErrorOwned::PixelValue(e.to_contained())
275 }
276 LayoutWidthParseError::InvalidKeyword(k) => {
277 LayoutWidthParseErrorOwned::InvalidKeyword(k.to_string())
278 }
279 }
280 }
281 }
282
283 impl LayoutWidthParseErrorOwned {
284 pub fn to_shared<'a>(&'a self) -> LayoutWidthParseError<'a> {
285 match self {
286 LayoutWidthParseErrorOwned::PixelValue(e) => {
287 LayoutWidthParseError::PixelValue(e.to_shared())
288 }
289 LayoutWidthParseErrorOwned::InvalidKeyword(k) => {
290 LayoutWidthParseError::InvalidKeyword(k)
291 }
292 }
293 }
294 }
295
296 pub fn parse_layout_width<'a>(
297 input: &'a str,
298 ) -> Result<LayoutWidth, LayoutWidthParseError<'a>> {
299 let trimmed = input.trim();
300 match trimmed {
301 "auto" => Ok(LayoutWidth::Auto),
302 "min-content" => Ok(LayoutWidth::MinContent),
303 "max-content" => Ok(LayoutWidth::MaxContent),
304 _ => parse_pixel_value(trimmed)
305 .map(LayoutWidth::Px)
306 .map_err(LayoutWidthParseError::PixelValue),
307 }
308 }
309
310 #[derive(Clone, PartialEq)]
311 pub enum LayoutHeightParseError<'a> {
312 PixelValue(CssPixelValueParseError<'a>),
313 InvalidKeyword(&'a str),
314 }
315
316 impl_debug_as_display!(LayoutHeightParseError<'a>);
317 impl_display! { LayoutHeightParseError<'a>, {
318 PixelValue(e) => format!("{}", e),
319 InvalidKeyword(k) => format!("Invalid height keyword: \"{}\"", k),
320 }}
321
322 impl_from! { CssPixelValueParseError<'a>, LayoutHeightParseError::PixelValue }
323
324 #[derive(Debug, Clone, PartialEq)]
325 pub enum LayoutHeightParseErrorOwned {
326 PixelValue(CssPixelValueParseErrorOwned),
327 InvalidKeyword(String),
328 }
329
330 impl<'a> LayoutHeightParseError<'a> {
331 pub fn to_contained(&self) -> LayoutHeightParseErrorOwned {
332 match self {
333 LayoutHeightParseError::PixelValue(e) => {
334 LayoutHeightParseErrorOwned::PixelValue(e.to_contained())
335 }
336 LayoutHeightParseError::InvalidKeyword(k) => {
337 LayoutHeightParseErrorOwned::InvalidKeyword(k.to_string())
338 }
339 }
340 }
341 }
342
343 impl LayoutHeightParseErrorOwned {
344 pub fn to_shared<'a>(&'a self) -> LayoutHeightParseError<'a> {
345 match self {
346 LayoutHeightParseErrorOwned::PixelValue(e) => {
347 LayoutHeightParseError::PixelValue(e.to_shared())
348 }
349 LayoutHeightParseErrorOwned::InvalidKeyword(k) => {
350 LayoutHeightParseError::InvalidKeyword(k)
351 }
352 }
353 }
354 }
355
356 pub fn parse_layout_height<'a>(
357 input: &'a str,
358 ) -> Result<LayoutHeight, LayoutHeightParseError<'a>> {
359 let trimmed = input.trim();
360 match trimmed {
361 "auto" => Ok(LayoutHeight::Auto),
362 "min-content" => Ok(LayoutHeight::MinContent),
363 "max-content" => Ok(LayoutHeight::MaxContent),
364 _ => parse_pixel_value(trimmed)
365 .map(LayoutHeight::Px)
366 .map_err(LayoutHeightParseError::PixelValue),
367 }
368 }
369 define_pixel_dimension_parser!(
370 parse_layout_min_width,
371 LayoutMinWidth,
372 LayoutMinWidthParseError,
373 LayoutMinWidthParseErrorOwned
374 );
375 define_pixel_dimension_parser!(
376 parse_layout_min_height,
377 LayoutMinHeight,
378 LayoutMinHeightParseError,
379 LayoutMinHeightParseErrorOwned
380 );
381 define_pixel_dimension_parser!(
382 parse_layout_max_width,
383 LayoutMaxWidth,
384 LayoutMaxWidthParseError,
385 LayoutMaxWidthParseErrorOwned
386 );
387 define_pixel_dimension_parser!(
388 parse_layout_max_height,
389 LayoutMaxHeight,
390 LayoutMaxHeightParseError,
391 LayoutMaxHeightParseErrorOwned
392 );
393
394 #[derive(Clone, PartialEq)]
397 pub enum LayoutBoxSizingParseError<'a> {
398 InvalidValue(&'a str),
399 }
400
401 impl_debug_as_display!(LayoutBoxSizingParseError<'a>);
402 impl_display! { LayoutBoxSizingParseError<'a>, {
403 InvalidValue(v) => format!("Invalid box-sizing value: \"{}\"", v),
404 }}
405
406 #[derive(Debug, Clone, PartialEq)]
407 pub enum LayoutBoxSizingParseErrorOwned {
408 InvalidValue(String),
409 }
410
411 impl<'a> LayoutBoxSizingParseError<'a> {
412 pub fn to_contained(&self) -> LayoutBoxSizingParseErrorOwned {
413 match self {
414 LayoutBoxSizingParseError::InvalidValue(s) => {
415 LayoutBoxSizingParseErrorOwned::InvalidValue(s.to_string())
416 }
417 }
418 }
419 }
420
421 impl LayoutBoxSizingParseErrorOwned {
422 pub fn to_shared<'a>(&'a self) -> LayoutBoxSizingParseError<'a> {
423 match self {
424 LayoutBoxSizingParseErrorOwned::InvalidValue(s) => {
425 LayoutBoxSizingParseError::InvalidValue(s)
426 }
427 }
428 }
429 }
430
431 pub fn parse_layout_box_sizing<'a>(
432 input: &'a str,
433 ) -> Result<LayoutBoxSizing, LayoutBoxSizingParseError<'a>> {
434 match input.trim() {
435 "content-box" => Ok(LayoutBoxSizing::ContentBox),
436 "border-box" => Ok(LayoutBoxSizing::BorderBox),
437 other => Err(LayoutBoxSizingParseError::InvalidValue(other)),
438 }
439 }
440}
441
442#[cfg(feature = "parser")]
443pub use self::parser::*;
444
445#[cfg(all(test, feature = "parser"))]
446mod tests {
447 use super::*;
448 use crate::props::basic::pixel::PixelValue;
449
450 #[test]
451 fn test_parse_layout_width() {
452 assert_eq!(
453 parse_layout_width("150px").unwrap(),
454 LayoutWidth::Px(PixelValue::px(150.0))
455 );
456 assert_eq!(
457 parse_layout_width("2.5em").unwrap(),
458 LayoutWidth::Px(PixelValue::em(2.5))
459 );
460 assert_eq!(
461 parse_layout_width("75%").unwrap(),
462 LayoutWidth::Px(PixelValue::percent(75.0))
463 );
464 assert_eq!(
465 parse_layout_width("0").unwrap(),
466 LayoutWidth::Px(PixelValue::px(0.0))
467 );
468 assert_eq!(
469 parse_layout_width(" 100pt ").unwrap(),
470 LayoutWidth::Px(PixelValue::pt(100.0))
471 );
472 assert_eq!(
473 parse_layout_width("min-content").unwrap(),
474 LayoutWidth::MinContent
475 );
476 assert_eq!(
477 parse_layout_width("max-content").unwrap(),
478 LayoutWidth::MaxContent
479 );
480 }
481
482 #[test]
483 fn test_parse_layout_height_invalid() {
484 assert!(parse_layout_height("auto").is_ok());
486 assert!(parse_layout_height("150 px").is_ok());
488 assert!(parse_layout_height("px").is_err());
489 assert!(parse_layout_height("invalid").is_err());
490 }
491
492 #[test]
493 fn test_parse_layout_box_sizing() {
494 assert_eq!(
495 parse_layout_box_sizing("content-box").unwrap(),
496 LayoutBoxSizing::ContentBox
497 );
498 assert_eq!(
499 parse_layout_box_sizing("border-box").unwrap(),
500 LayoutBoxSizing::BorderBox
501 );
502 assert_eq!(
503 parse_layout_box_sizing(" border-box ").unwrap(),
504 LayoutBoxSizing::BorderBox
505 );
506 }
507
508 #[test]
509 fn test_parse_layout_box_sizing_invalid() {
510 assert!(parse_layout_box_sizing("padding-box").is_err());
511 assert!(parse_layout_box_sizing("borderbox").is_err());
512 assert!(parse_layout_box_sizing("").is_err());
513 }
514}