1use alloc::string::{String, ToString};
4use core::num::ParseFloatError;
5use crate::corety::AzString;
6
7use crate::{
8 codegen::format::FormatAsRustCode,
9 props::{
10 basic::{
11 error::ParseFloatErrorWithInput,
12 length::{parse_float_value, FloatValue},
13 },
14 formatter::PrintAsCssValue,
15 },
16};
17
18#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[repr(C)]
25pub struct LayoutFlexGrow {
26 pub inner: FloatValue,
27}
28
29impl core::fmt::Debug for LayoutFlexGrow {
30 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31 write!(f, "{}", self.inner.get())
32 }
33}
34
35impl Default for LayoutFlexGrow {
36 fn default() -> Self {
37 Self {
38 inner: FloatValue::const_new(0),
39 }
40 }
41}
42
43impl PrintAsCssValue for LayoutFlexGrow {
44 fn print_as_css_value(&self) -> String {
45 format!("{}", self.inner)
46 }
47}
48
49impl LayoutFlexGrow {
50 #[must_use] pub fn new(value: isize) -> Self {
51 Self {
52 inner: FloatValue::new(crate::cast::isize_to_f32(value)),
53 }
54 }
55
56 #[must_use] pub const fn const_new(value: isize) -> Self {
57 Self {
58 inner: FloatValue::const_new(value),
59 }
60 }
61
62 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
63 Self {
64 inner: self.inner.interpolate(&other.inner, t),
65 }
66 }
67}
68
69#[cfg(feature = "parser")]
70#[derive(Clone, PartialEq, Eq)]
71pub enum FlexGrowParseError<'a> {
72 ParseFloat(ParseFloatError, &'a str),
73 NegativeValue(&'a str),
74}
75
76#[cfg(feature = "parser")]
77impl_debug_as_display!(FlexGrowParseError<'a>);
78#[cfg(feature = "parser")]
79impl_display! { FlexGrowParseError<'a>, {
80 ParseFloat(e, s) => format!("Invalid flex-grow value: \"{}\". Reason: {}", s, e),
81 NegativeValue(s) => format!("Invalid flex-grow value: \"{}\". Flex-grow cannot be negative", s),
82}}
83
84#[cfg(feature = "parser")]
85#[derive(Debug, Clone, PartialEq, Eq)]
86#[repr(C, u8)]
87pub enum FlexGrowParseErrorOwned {
88 ParseFloat(ParseFloatErrorWithInput),
89 NegativeValue(AzString),
90}
91
92#[cfg(feature = "parser")]
93impl FlexGrowParseError<'_> {
94 #[must_use] pub fn to_contained(&self) -> FlexGrowParseErrorOwned {
95 match self {
96 FlexGrowParseError::ParseFloat(e, s) => {
97 FlexGrowParseErrorOwned::ParseFloat(ParseFloatErrorWithInput { error: e.clone().into(), input: (*s).to_string().into() })
98 }
99 FlexGrowParseError::NegativeValue(s) => {
100 FlexGrowParseErrorOwned::NegativeValue((*s).to_string().into())
101 }
102 }
103 }
104}
105
106#[cfg(feature = "parser")]
107impl FlexGrowParseErrorOwned {
108 #[must_use] pub fn to_shared(&self) -> FlexGrowParseError<'_> {
109 match self {
110 Self::ParseFloat(e) => {
111 FlexGrowParseError::ParseFloat(e.error.to_std(), e.input.as_str())
112 }
113 Self::NegativeValue(s) => {
114 FlexGrowParseError::NegativeValue(s.as_str())
115 }
116 }
117 }
118}
119
120#[cfg(feature = "parser")]
121pub fn parse_layout_flex_grow(
125 input: &str,
126) -> Result<LayoutFlexGrow, FlexGrowParseError<'_>> {
127 match parse_float_value(input) {
128 Ok(o) => {
129 if o.get() < 0.0 {
130 Err(FlexGrowParseError::NegativeValue(input))
131 } else {
132 Ok(LayoutFlexGrow { inner: o })
133 }
134 }
135 Err(e) => Err(FlexGrowParseError::ParseFloat(e, input)),
136 }
137}
138
139#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
145#[repr(C)]
146pub struct LayoutFlexShrink {
147 pub inner: FloatValue,
148}
149
150impl core::fmt::Debug for LayoutFlexShrink {
151 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152 write!(f, "{}", self.inner.get())
153 }
154}
155
156impl Default for LayoutFlexShrink {
157 fn default() -> Self {
158 Self {
159 inner: FloatValue::const_new(1),
160 }
161 }
162}
163
164impl PrintAsCssValue for LayoutFlexShrink {
165 fn print_as_css_value(&self) -> String {
166 format!("{}", self.inner)
167 }
168}
169
170impl LayoutFlexShrink {
171 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
172 Self {
173 inner: self.inner.interpolate(&other.inner, t),
174 }
175 }
176}
177
178#[cfg(feature = "parser")]
179#[derive(Clone, PartialEq, Eq)]
180pub enum FlexShrinkParseError<'a> {
181 ParseFloat(ParseFloatError, &'a str),
182 NegativeValue(&'a str),
183}
184
185#[cfg(feature = "parser")]
186impl_debug_as_display!(FlexShrinkParseError<'a>);
187#[cfg(feature = "parser")]
188impl_display! { FlexShrinkParseError<'a>, {
189 ParseFloat(e, s) => format!("Invalid flex-shrink value: \"{}\". Reason: {}", s, e),
190 NegativeValue(s) => format!("Invalid flex-shrink value: \"{}\". Flex-shrink cannot be negative", s),
191}}
192
193#[cfg(feature = "parser")]
194#[derive(Debug, Clone, PartialEq, Eq)]
195#[repr(C, u8)]
196pub enum FlexShrinkParseErrorOwned {
197 ParseFloat(ParseFloatErrorWithInput),
198 NegativeValue(AzString),
199}
200
201#[cfg(feature = "parser")]
202impl FlexShrinkParseError<'_> {
203 #[must_use] pub fn to_contained(&self) -> FlexShrinkParseErrorOwned {
204 match self {
205 FlexShrinkParseError::ParseFloat(e, s) => {
206 FlexShrinkParseErrorOwned::ParseFloat(ParseFloatErrorWithInput { error: e.clone().into(), input: (*s).to_string().into() })
207 }
208 FlexShrinkParseError::NegativeValue(s) => {
209 FlexShrinkParseErrorOwned::NegativeValue((*s).to_string().into())
210 }
211 }
212 }
213}
214
215#[cfg(feature = "parser")]
216impl FlexShrinkParseErrorOwned {
217 #[must_use] pub fn to_shared(&self) -> FlexShrinkParseError<'_> {
218 match self {
219 Self::ParseFloat(e) => {
220 FlexShrinkParseError::ParseFloat(e.error.to_std(), e.input.as_str())
221 }
222 Self::NegativeValue(s) => {
223 FlexShrinkParseError::NegativeValue(s.as_str())
224 }
225 }
226 }
227}
228
229#[cfg(feature = "parser")]
230pub fn parse_layout_flex_shrink(
234 input: &str,
235) -> Result<LayoutFlexShrink, FlexShrinkParseError<'_>> {
236 match parse_float_value(input) {
237 Ok(o) => {
238 if o.get() < 0.0 {
239 Err(FlexShrinkParseError::NegativeValue(input))
240 } else {
241 Ok(LayoutFlexShrink { inner: o })
242 }
243 }
244 Err(e) => Err(FlexShrinkParseError::ParseFloat(e, input)),
245 }
246}
247
248#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254#[repr(C)]
255#[derive(Default)]
256pub enum LayoutFlexDirection {
257 #[default]
258 Row,
259 RowReverse,
260 Column,
261 ColumnReverse,
262}
263
264
265#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
267#[repr(C)]
268pub enum LayoutAxis {
269 Horizontal,
270 Vertical,
271}
272
273impl LayoutFlexDirection {
274 #[must_use] pub const fn get_axis(&self) -> LayoutAxis {
275 match self {
276 Self::Row | Self::RowReverse => LayoutAxis::Horizontal,
277 Self::Column | Self::ColumnReverse => LayoutAxis::Vertical,
278 }
279 }
280
281 #[must_use] pub const fn is_reverse(&self) -> bool {
282 matches!(self, Self::RowReverse | Self::ColumnReverse)
283 }
284}
285
286impl PrintAsCssValue for LayoutFlexDirection {
287 fn print_as_css_value(&self) -> String {
288 String::from(match self {
289 Self::Row => "row",
290 Self::RowReverse => "row-reverse",
291 Self::Column => "column",
292 Self::ColumnReverse => "column-reverse",
293 })
294 }
295}
296
297#[cfg(feature = "parser")]
298#[derive(Clone, PartialEq, Eq)]
299pub enum FlexDirectionParseError<'a> {
300 InvalidValue(&'a str),
301}
302
303#[cfg(feature = "parser")]
304impl_debug_as_display!(FlexDirectionParseError<'a>);
305#[cfg(feature = "parser")]
306impl_display! { FlexDirectionParseError<'a>, {
307 InvalidValue(s) => format!("Invalid flex-direction value: \"{}\"", s),
308}}
309
310#[cfg(feature = "parser")]
311#[derive(Debug, Clone, PartialEq, Eq)]
312#[repr(C, u8)]
313pub enum FlexDirectionParseErrorOwned {
314 InvalidValue(AzString),
315}
316
317#[cfg(feature = "parser")]
318impl FlexDirectionParseError<'_> {
319 #[must_use] pub fn to_contained(&self) -> FlexDirectionParseErrorOwned {
320 match self {
321 Self::InvalidValue(s) => FlexDirectionParseErrorOwned::InvalidValue((*s).to_string().into()),
322 }
323 }
324}
325
326#[cfg(feature = "parser")]
327impl FlexDirectionParseErrorOwned {
328 #[must_use] pub fn to_shared(&self) -> FlexDirectionParseError<'_> {
329 match self {
330 Self::InvalidValue(s) => FlexDirectionParseError::InvalidValue(s.as_str()),
331 }
332 }
333}
334
335#[cfg(feature = "parser")]
336pub fn parse_layout_flex_direction(
340 input: &str,
341) -> Result<LayoutFlexDirection, FlexDirectionParseError<'_>> {
342 match input.trim() {
343 "row" => Ok(LayoutFlexDirection::Row),
344 "row-reverse" => Ok(LayoutFlexDirection::RowReverse),
345 "column" => Ok(LayoutFlexDirection::Column),
346 "column-reverse" => Ok(LayoutFlexDirection::ColumnReverse),
347 _ => Err(FlexDirectionParseError::InvalidValue(input)),
348 }
349}
350
351#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357#[repr(C)]
358#[derive(Default)]
359pub enum LayoutFlexWrap {
360 Wrap,
361 #[default]
362 NoWrap,
363 WrapReverse,
364}
365
366
367impl PrintAsCssValue for LayoutFlexWrap {
368 fn print_as_css_value(&self) -> String {
369 String::from(match self {
370 Self::Wrap => "wrap",
371 Self::NoWrap => "nowrap",
372 Self::WrapReverse => "wrap-reverse",
373 })
374 }
375}
376
377#[cfg(feature = "parser")]
378#[derive(Clone, PartialEq, Eq)]
379pub enum FlexWrapParseError<'a> {
380 InvalidValue(&'a str),
381}
382
383#[cfg(feature = "parser")]
384impl_debug_as_display!(FlexWrapParseError<'a>);
385#[cfg(feature = "parser")]
386impl_display! { FlexWrapParseError<'a>, {
387 InvalidValue(s) => format!("Invalid flex-wrap value: \"{}\"", s),
388}}
389
390#[cfg(feature = "parser")]
391#[derive(Debug, Clone, PartialEq, Eq)]
392#[repr(C, u8)]
393pub enum FlexWrapParseErrorOwned {
394 InvalidValue(AzString),
395}
396
397#[cfg(feature = "parser")]
398impl FlexWrapParseError<'_> {
399 #[must_use] pub fn to_contained(&self) -> FlexWrapParseErrorOwned {
400 match self {
401 Self::InvalidValue(s) => FlexWrapParseErrorOwned::InvalidValue((*s).to_string().into()),
402 }
403 }
404}
405
406#[cfg(feature = "parser")]
407impl FlexWrapParseErrorOwned {
408 #[must_use] pub fn to_shared(&self) -> FlexWrapParseError<'_> {
409 match self {
410 Self::InvalidValue(s) => FlexWrapParseError::InvalidValue(s.as_str()),
411 }
412 }
413}
414
415#[cfg(feature = "parser")]
416pub fn parse_layout_flex_wrap(
420 input: &str,
421) -> Result<LayoutFlexWrap, FlexWrapParseError<'_>> {
422 match input.trim() {
423 "wrap" => Ok(LayoutFlexWrap::Wrap),
424 "nowrap" => Ok(LayoutFlexWrap::NoWrap),
425 "wrap-reverse" => Ok(LayoutFlexWrap::WrapReverse),
426 _ => Err(FlexWrapParseError::InvalidValue(input)),
427 }
428}
429
430#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436#[repr(C)]
437#[derive(Default)]
438pub enum LayoutJustifyContent {
439 FlexStart,
440 FlexEnd,
441 #[default]
442 Start,
443 End,
444 Center,
445 SpaceBetween,
446 SpaceAround,
447 SpaceEvenly,
448}
449
450
451impl PrintAsCssValue for LayoutJustifyContent {
452 fn print_as_css_value(&self) -> String {
453 String::from(match self {
454 Self::Start => "start",
455 Self::End => "end",
456 Self::FlexStart => "flex-start",
457 Self::FlexEnd => "flex-end",
458 Self::Center => "center",
459 Self::SpaceBetween => "space-between",
460 Self::SpaceAround => "space-around",
461 Self::SpaceEvenly => "space-evenly",
462 })
463 }
464}
465
466#[cfg(feature = "parser")]
467#[derive(Clone, PartialEq, Eq)]
468pub enum JustifyContentParseError<'a> {
469 InvalidValue(&'a str),
470}
471
472#[cfg(feature = "parser")]
473impl_debug_as_display!(JustifyContentParseError<'a>);
474#[cfg(feature = "parser")]
475impl_display! { JustifyContentParseError<'a>, {
476 InvalidValue(s) => format!("Invalid justify-content value: \"{}\"", s),
477}}
478
479#[cfg(feature = "parser")]
480#[derive(Debug, Clone, PartialEq, Eq)]
481#[repr(C, u8)]
482pub enum JustifyContentParseErrorOwned {
483 InvalidValue(AzString),
484}
485
486#[cfg(feature = "parser")]
487impl JustifyContentParseError<'_> {
488 #[must_use] pub fn to_contained(&self) -> JustifyContentParseErrorOwned {
489 match self {
490 Self::InvalidValue(s) => JustifyContentParseErrorOwned::InvalidValue((*s).to_string().into()),
491 }
492 }
493}
494
495#[cfg(feature = "parser")]
496impl JustifyContentParseErrorOwned {
497 #[must_use] pub fn to_shared(&self) -> JustifyContentParseError<'_> {
498 match self {
499 Self::InvalidValue(s) => JustifyContentParseError::InvalidValue(s.as_str()),
500 }
501 }
502}
503
504#[cfg(feature = "parser")]
505pub fn parse_layout_justify_content(
509 input: &str,
510) -> Result<LayoutJustifyContent, JustifyContentParseError<'_>> {
511 match input.trim() {
512 "flex-start" => Ok(LayoutJustifyContent::FlexStart),
513 "flex-end" => Ok(LayoutJustifyContent::FlexEnd),
514 "start" => Ok(LayoutJustifyContent::Start),
515 "end" => Ok(LayoutJustifyContent::End),
516 "center" => Ok(LayoutJustifyContent::Center),
517 "space-between" => Ok(LayoutJustifyContent::SpaceBetween),
518 "space-around" => Ok(LayoutJustifyContent::SpaceAround),
519 "space-evenly" => Ok(LayoutJustifyContent::SpaceEvenly),
520 _ => Err(JustifyContentParseError::InvalidValue(input)),
521 }
522}
523
524#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
530#[repr(C)]
531#[derive(Default)]
532pub enum LayoutAlignItems {
533 #[default]
534 Stretch,
535 Center,
536 Start,
537 End,
538 Baseline,
539}
540
541
542impl PrintAsCssValue for LayoutAlignItems {
543 fn print_as_css_value(&self) -> String {
544 String::from(match self {
545 Self::Stretch => "stretch",
546 Self::Center => "center",
547 Self::Start => "flex-start",
548 Self::End => "flex-end",
549 Self::Baseline => "baseline",
550 })
551 }
552}
553
554#[cfg(feature = "parser")]
555#[derive(Clone, PartialEq, Eq)]
556pub enum AlignItemsParseError<'a> {
557 InvalidValue(&'a str),
558}
559
560#[cfg(feature = "parser")]
561impl_debug_as_display!(AlignItemsParseError<'a>);
562#[cfg(feature = "parser")]
563impl_display! { AlignItemsParseError<'a>, {
564 InvalidValue(s) => format!("Invalid align-items value: \"{}\"", s),
565}}
566
567#[cfg(feature = "parser")]
568#[derive(Debug, Clone, PartialEq, Eq)]
569#[repr(C, u8)]
570pub enum AlignItemsParseErrorOwned {
571 InvalidValue(AzString),
572}
573
574#[cfg(feature = "parser")]
575impl AlignItemsParseError<'_> {
576 #[must_use] pub fn to_contained(&self) -> AlignItemsParseErrorOwned {
577 match self {
578 Self::InvalidValue(s) => AlignItemsParseErrorOwned::InvalidValue((*s).to_string().into()),
579 }
580 }
581}
582
583#[cfg(feature = "parser")]
584impl AlignItemsParseErrorOwned {
585 #[must_use] pub fn to_shared(&self) -> AlignItemsParseError<'_> {
586 match self {
587 Self::InvalidValue(s) => AlignItemsParseError::InvalidValue(s.as_str()),
588 }
589 }
590}
591
592#[cfg(feature = "parser")]
593pub fn parse_layout_align_items(
597 input: &str,
598) -> Result<LayoutAlignItems, AlignItemsParseError<'_>> {
599 match input.trim() {
600 "stretch" => Ok(LayoutAlignItems::Stretch),
601 "center" => Ok(LayoutAlignItems::Center),
602 "start" | "flex-start" => Ok(LayoutAlignItems::Start),
603 "end" | "flex-end" => Ok(LayoutAlignItems::End),
604 "baseline" => Ok(LayoutAlignItems::Baseline),
605 _ => Err(AlignItemsParseError::InvalidValue(input)),
606 }
607}
608
609#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
615#[repr(C)]
616#[derive(Default)]
617pub enum LayoutAlignContent {
618 #[default]
619 Stretch,
620 Center,
621 Start,
622 End,
623 SpaceBetween,
624 SpaceAround,
625}
626
627
628impl PrintAsCssValue for LayoutAlignContent {
629 fn print_as_css_value(&self) -> String {
630 String::from(match self {
631 Self::Stretch => "stretch",
632 Self::Center => "center",
633 Self::Start => "flex-start",
634 Self::End => "flex-end",
635 Self::SpaceBetween => "space-between",
636 Self::SpaceAround => "space-around",
637 })
638 }
639}
640
641#[cfg(feature = "parser")]
642#[derive(Clone, PartialEq, Eq)]
643pub enum AlignContentParseError<'a> {
644 InvalidValue(&'a str),
645}
646
647#[cfg(feature = "parser")]
648impl_debug_as_display!(AlignContentParseError<'a>);
649#[cfg(feature = "parser")]
650impl_display! { AlignContentParseError<'a>, {
651 InvalidValue(s) => format!("Invalid align-content value: \"{}\"", s),
652}}
653
654#[cfg(feature = "parser")]
655#[derive(Debug, Clone, PartialEq, Eq)]
656#[repr(C, u8)]
657pub enum AlignContentParseErrorOwned {
658 InvalidValue(AzString),
659}
660
661#[cfg(feature = "parser")]
662impl AlignContentParseError<'_> {
663 #[must_use] pub fn to_contained(&self) -> AlignContentParseErrorOwned {
664 match self {
665 Self::InvalidValue(s) => AlignContentParseErrorOwned::InvalidValue((*s).to_string().into()),
666 }
667 }
668}
669
670#[cfg(feature = "parser")]
671impl AlignContentParseErrorOwned {
672 #[must_use] pub fn to_shared(&self) -> AlignContentParseError<'_> {
673 match self {
674 Self::InvalidValue(s) => AlignContentParseError::InvalidValue(s.as_str()),
675 }
676 }
677}
678
679#[cfg(feature = "parser")]
680pub fn parse_layout_align_content(
684 input: &str,
685) -> Result<LayoutAlignContent, AlignContentParseError<'_>> {
686 match input.trim() {
687 "stretch" => Ok(LayoutAlignContent::Stretch),
688 "center" => Ok(LayoutAlignContent::Center),
689 "start" | "flex-start" => Ok(LayoutAlignContent::Start),
690 "end" | "flex-end" => Ok(LayoutAlignContent::End),
691 "space-between" => Ok(LayoutAlignContent::SpaceBetween),
692 "space-around" => Ok(LayoutAlignContent::SpaceAround),
693 _ => Err(AlignContentParseError::InvalidValue(input)),
694 }
695}
696
697#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
703#[repr(C)]
704#[derive(Default)]
705pub enum LayoutAlignSelf {
706 #[default]
707 Auto,
708 Stretch,
709 Center,
710 Start,
711 End,
712 Baseline,
713}
714
715
716impl PrintAsCssValue for LayoutAlignSelf {
717 fn print_as_css_value(&self) -> String {
718 String::from(match self {
719 Self::Auto => "auto",
720 Self::Stretch => "stretch",
721 Self::Center => "center",
722 Self::Start => "flex-start",
723 Self::End => "flex-end",
724 Self::Baseline => "baseline",
725 })
726 }
727}
728
729impl FormatAsRustCode for LayoutAlignSelf {
730 fn format_as_rust_code(&self, _tabs: usize) -> String {
731 format!(
732 "LayoutAlignSelf::{}",
733 match self {
734 Self::Auto => "Auto",
735 Self::Stretch => "Stretch",
736 Self::Center => "Center",
737 Self::Start => "Start",
738 Self::End => "End",
739 Self::Baseline => "Baseline",
740 }
741 )
742 }
743}
744
745#[cfg(feature = "parser")]
746#[derive(Clone, PartialEq, Eq)]
747pub enum AlignSelfParseError<'a> {
748 InvalidValue(&'a str),
749}
750
751#[cfg(feature = "parser")]
752impl_debug_as_display!(AlignSelfParseError<'a>);
753#[cfg(feature = "parser")]
754impl_display! { AlignSelfParseError<'a>, {
755 InvalidValue(s) => format!("Invalid align-self value: \"{}\"", s),
756}}
757
758#[cfg(feature = "parser")]
759#[derive(Debug, Clone, PartialEq, Eq)]
760#[repr(C, u8)]
761pub enum AlignSelfParseErrorOwned {
762 InvalidValue(AzString),
763}
764
765#[cfg(feature = "parser")]
766impl AlignSelfParseError<'_> {
767 #[must_use] pub fn to_contained(&self) -> AlignSelfParseErrorOwned {
768 match self {
769 Self::InvalidValue(s) => AlignSelfParseErrorOwned::InvalidValue((*s).to_string().into()),
770 }
771 }
772}
773
774#[cfg(feature = "parser")]
775impl AlignSelfParseErrorOwned {
776 #[must_use] pub fn to_shared(&self) -> AlignSelfParseError<'_> {
777 match self {
778 Self::InvalidValue(s) => AlignSelfParseError::InvalidValue(s.as_str()),
779 }
780 }
781}
782
783#[cfg(feature = "parser")]
784pub fn parse_layout_align_self(
788 input: &str,
789) -> Result<LayoutAlignSelf, AlignSelfParseError<'_>> {
790 match input.trim() {
791 "auto" => Ok(LayoutAlignSelf::Auto),
792 "stretch" => Ok(LayoutAlignSelf::Stretch),
793 "center" => Ok(LayoutAlignSelf::Center),
794 "start" | "flex-start" => Ok(LayoutAlignSelf::Start),
795 "end" | "flex-end" => Ok(LayoutAlignSelf::End),
796 "baseline" => Ok(LayoutAlignSelf::Baseline),
797 _ => Err(AlignSelfParseError::InvalidValue(input)),
798 }
799}
800
801#[allow(variant_size_differences)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
805#[repr(C, u8)]
806#[derive(Default)]
807pub enum LayoutFlexBasis {
808 #[default]
810 Auto,
811 Exact(crate::props::basic::pixel::PixelValue),
813}
814
815impl core::fmt::Debug for LayoutFlexBasis {
816 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
817 write!(f, "{}", self.print_as_css_value())
818 }
819}
820
821
822impl PrintAsCssValue for LayoutFlexBasis {
823 fn print_as_css_value(&self) -> String {
824 match self {
825 Self::Auto => "auto".to_string(),
826 Self::Exact(px) => px.print_as_css_value(),
827 }
828 }
829}
830
831impl FormatAsRustCode for LayoutFlexBasis {
832 fn format_as_rust_code(&self, _tabs: usize) -> String {
833 match self {
834 Self::Auto => String::from("LayoutFlexBasis::Auto"),
835 Self::Exact(px) => {
836 format!(
837 "LayoutFlexBasis::Exact({})",
838 crate::codegen::format::format_pixel_value(px)
839 )
840 }
841 }
842 }
843}
844
845#[cfg(feature = "parser")]
846#[derive(Clone, PartialEq, Eq)]
847pub enum FlexBasisParseError<'a> {
848 InvalidValue(&'a str),
849}
850
851#[cfg(feature = "parser")]
852impl_debug_as_display!(FlexBasisParseError<'a>);
853#[cfg(feature = "parser")]
854impl_display! { FlexBasisParseError<'a>, {
855 InvalidValue(e) => format!("Invalid flex-basis value: \"{}\"", e),
856}}
857
858#[cfg(feature = "parser")]
859#[derive(Debug, Clone, PartialEq, Eq)]
860#[repr(C, u8)]
861pub enum FlexBasisParseErrorOwned {
862 InvalidValue(AzString),
863}
864
865#[cfg(feature = "parser")]
866impl FlexBasisParseError<'_> {
867 #[must_use] pub fn to_contained(&self) -> FlexBasisParseErrorOwned {
868 match self {
869 FlexBasisParseError::InvalidValue(s) => {
870 FlexBasisParseErrorOwned::InvalidValue((*s).to_string().into())
871 }
872 }
873 }
874}
875
876#[cfg(feature = "parser")]
877impl FlexBasisParseErrorOwned {
878 #[must_use] pub fn to_shared(&self) -> FlexBasisParseError<'_> {
879 match self {
880 Self::InvalidValue(s) => {
881 FlexBasisParseError::InvalidValue(s.as_str())
882 }
883 }
884 }
885}
886
887#[cfg(feature = "parser")]
888pub fn parse_layout_flex_basis(
892 input: &str,
893) -> Result<LayoutFlexBasis, FlexBasisParseError<'_>> {
894 use crate::props::basic::pixel::parse_pixel_value;
895
896 match input.trim() {
897 "auto" => Ok(LayoutFlexBasis::Auto),
898 s => parse_pixel_value(s)
899 .map(LayoutFlexBasis::Exact)
900 .map_err(|_| FlexBasisParseError::InvalidValue(input)),
901 }
902}
903
904#[cfg(all(test, feature = "parser"))]
905mod tests {
906 #![allow(clippy::float_cmp)]
908 use super::*;
909 use crate::props::basic::pixel::PixelValue;
910
911 #[test]
912 fn test_parse_layout_flex_grow() {
913 assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
914 assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
915 assert_eq!(parse_layout_flex_grow("2.5").unwrap().inner.get(), 2.5);
916 assert_eq!(parse_layout_flex_grow(" 0.5 ").unwrap().inner.get(), 0.5);
917 assert!(parse_layout_flex_grow("none").is_err());
918 assert!(parse_layout_flex_grow("-1").is_err()); }
920
921 #[test]
922 fn test_parse_layout_flex_shrink() {
923 assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
924 assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
925 assert_eq!(parse_layout_flex_shrink("3.0").unwrap().inner.get(), 3.0);
926 assert_eq!(parse_layout_flex_shrink(" 0.2 ").unwrap().inner.get(), 0.2);
927 assert!(parse_layout_flex_shrink("auto").is_err());
928 assert!(parse_layout_flex_shrink("-1").is_err()); }
930
931 #[test]
932 fn test_parse_layout_flex_direction() {
933 assert_eq!(
934 parse_layout_flex_direction("row").unwrap(),
935 LayoutFlexDirection::Row
936 );
937 assert_eq!(
938 parse_layout_flex_direction("row-reverse").unwrap(),
939 LayoutFlexDirection::RowReverse
940 );
941 assert_eq!(
942 parse_layout_flex_direction("column").unwrap(),
943 LayoutFlexDirection::Column
944 );
945 assert_eq!(
946 parse_layout_flex_direction("column-reverse").unwrap(),
947 LayoutFlexDirection::ColumnReverse
948 );
949 assert_eq!(
950 parse_layout_flex_direction(" row ").unwrap(),
951 LayoutFlexDirection::Row
952 );
953 assert!(parse_layout_flex_direction("reversed-row").is_err());
954 }
955
956 #[test]
957 fn test_parse_layout_flex_wrap() {
958 assert_eq!(
959 parse_layout_flex_wrap("nowrap").unwrap(),
960 LayoutFlexWrap::NoWrap
961 );
962 assert_eq!(
963 parse_layout_flex_wrap("wrap").unwrap(),
964 LayoutFlexWrap::Wrap
965 );
966 assert_eq!(
967 parse_layout_flex_wrap("wrap-reverse").unwrap(),
968 LayoutFlexWrap::WrapReverse
969 );
970 assert_eq!(
971 parse_layout_flex_wrap(" wrap ").unwrap(),
972 LayoutFlexWrap::Wrap
973 );
974 assert!(parse_layout_flex_wrap("wrap reverse").is_err());
975 }
976
977 #[test]
978 fn test_parse_layout_justify_content() {
979 assert_eq!(
980 parse_layout_justify_content("flex-start").unwrap(),
981 LayoutJustifyContent::FlexStart
982 );
983 assert_eq!(
984 parse_layout_justify_content("flex-end").unwrap(),
985 LayoutJustifyContent::FlexEnd
986 );
987 assert_eq!(
988 parse_layout_justify_content("start").unwrap(),
989 LayoutJustifyContent::Start
990 );
991 assert_eq!(
992 parse_layout_justify_content("end").unwrap(),
993 LayoutJustifyContent::End
994 );
995 assert_eq!(
996 parse_layout_justify_content("center").unwrap(),
997 LayoutJustifyContent::Center
998 );
999 assert_eq!(
1000 parse_layout_justify_content("space-between").unwrap(),
1001 LayoutJustifyContent::SpaceBetween
1002 );
1003 assert_eq!(
1004 parse_layout_justify_content("space-around").unwrap(),
1005 LayoutJustifyContent::SpaceAround
1006 );
1007 assert_eq!(
1008 parse_layout_justify_content("space-evenly").unwrap(),
1009 LayoutJustifyContent::SpaceEvenly
1010 );
1011 assert_eq!(
1012 parse_layout_justify_content(" center ").unwrap(),
1013 LayoutJustifyContent::Center
1014 );
1015 }
1016
1017 #[test]
1018 fn test_parse_layout_align_items() {
1019 assert_eq!(
1020 parse_layout_align_items("stretch").unwrap(),
1021 LayoutAlignItems::Stretch
1022 );
1023 assert_eq!(
1024 parse_layout_align_items("flex-start").unwrap(),
1025 LayoutAlignItems::Start
1026 );
1027 assert_eq!(
1028 parse_layout_align_items("flex-end").unwrap(),
1029 LayoutAlignItems::End
1030 );
1031 assert_eq!(
1032 parse_layout_align_items("start").unwrap(),
1033 LayoutAlignItems::Start
1034 );
1035 assert_eq!(
1036 parse_layout_align_items("end").unwrap(),
1037 LayoutAlignItems::End
1038 );
1039 assert_eq!(
1040 parse_layout_align_items("center").unwrap(),
1041 LayoutAlignItems::Center
1042 );
1043 assert_eq!(
1044 parse_layout_align_items("baseline").unwrap(),
1045 LayoutAlignItems::Baseline
1046 );
1047 assert!(parse_layout_align_items("invalid").is_err());
1048 }
1049
1050 #[test]
1051 fn test_parse_layout_align_content() {
1052 assert_eq!(
1053 parse_layout_align_content("stretch").unwrap(),
1054 LayoutAlignContent::Stretch
1055 );
1056 assert_eq!(
1057 parse_layout_align_content("flex-start").unwrap(),
1058 LayoutAlignContent::Start
1059 );
1060 assert_eq!(
1061 parse_layout_align_content("flex-end").unwrap(),
1062 LayoutAlignContent::End
1063 );
1064 assert_eq!(
1065 parse_layout_align_content("center").unwrap(),
1066 LayoutAlignContent::Center
1067 );
1068 assert_eq!(
1069 parse_layout_align_content("space-between").unwrap(),
1070 LayoutAlignContent::SpaceBetween
1071 );
1072 assert_eq!(
1073 parse_layout_align_content("space-around").unwrap(),
1074 LayoutAlignContent::SpaceAround
1075 );
1076 assert!(parse_layout_align_content("space-evenly").is_err()); }
1078
1079 #[test]
1080 fn test_parse_layout_flex_basis() {
1081 assert_eq!(
1082 parse_layout_flex_basis("auto").unwrap(),
1083 LayoutFlexBasis::Auto
1084 );
1085 assert_eq!(
1086 parse_layout_flex_basis("200px").unwrap(),
1087 LayoutFlexBasis::Exact(PixelValue::px(200.0))
1088 );
1089 assert_eq!(
1090 parse_layout_flex_basis("50%").unwrap(),
1091 LayoutFlexBasis::Exact(PixelValue::percent(50.0))
1092 );
1093 assert_eq!(
1094 parse_layout_flex_basis(" 10em ").unwrap(),
1095 LayoutFlexBasis::Exact(PixelValue::em(10.0))
1096 );
1097 assert!(parse_layout_flex_basis("none").is_err());
1098 assert_eq!(
1100 parse_layout_flex_basis("200").unwrap(),
1101 LayoutFlexBasis::Exact(PixelValue::px(200.0))
1102 );
1103 assert_eq!(
1104 parse_layout_flex_basis("0").unwrap(),
1105 LayoutFlexBasis::Exact(PixelValue::px(0.0))
1106 );
1107 }
1108}
1109
1110#[cfg(test)]
1111#[allow(clippy::float_cmp)] mod autotest_generated {
1113 use super::*;
1114 use crate::props::basic::{length::FloatValue, pixel::PixelValue};
1115
1116 #[test]
1124 fn flex_grow_new_agrees_with_const_new_for_exact_ints() {
1125 for v in [-10_000_isize, -1000, -7, -1, 0, 1, 7, 1000, 10_000] {
1126 assert_eq!(
1127 LayoutFlexGrow::new(v).inner.number(),
1128 LayoutFlexGrow::const_new(v).inner.number(),
1129 "new()/const_new() disagree for {v}"
1130 );
1131 assert_eq!(LayoutFlexGrow::new(v).inner.get(), v as f32);
1132 }
1133 }
1134
1135 #[test]
1138 fn flex_grow_new_saturates_at_isize_extremes() {
1139 let max = LayoutFlexGrow::new(isize::MAX);
1140 let min = LayoutFlexGrow::new(isize::MIN);
1141
1142 assert_eq!(max.inner.number(), isize::MAX, "MAX must saturate, not wrap");
1143 assert_eq!(min.inner.number(), isize::MIN, "MIN must saturate, not wrap");
1144 assert!(max.inner.get().is_finite());
1145 assert!(min.inner.get().is_finite());
1146 assert!(max.inner.get() > 0.0);
1147 assert!(min.inner.get() < 0.0);
1148 }
1149
1150 #[test]
1155 fn flex_grow_const_new_at_safe_encoding_boundary() {
1156 let hi = isize::MAX / 1000;
1157 let lo = isize::MIN / 1000;
1158
1159 assert_eq!(LayoutFlexGrow::const_new(hi).inner.number(), hi * 1000);
1160 assert_eq!(LayoutFlexGrow::const_new(lo).inner.number(), lo * 1000);
1161 assert!(LayoutFlexGrow::const_new(hi).inner.get().is_finite());
1162 assert!(LayoutFlexGrow::const_new(lo).inner.get().is_finite());
1163 }
1164
1165 #[test]
1168 fn flex_grow_const_new_zero_and_negative_are_not_clamped() {
1169 const ZERO: LayoutFlexGrow = LayoutFlexGrow::const_new(0);
1170 const NEG: LayoutFlexGrow = LayoutFlexGrow::const_new(-3);
1171
1172 assert_eq!(ZERO.inner.get(), 0.0);
1173 assert_eq!(ZERO.inner.number(), 0);
1174 assert_eq!(NEG.inner.get(), -3.0);
1175 assert_eq!(LayoutFlexGrow::new(-3).inner.get(), -3.0);
1176 }
1177
1178 #[test]
1179 fn flex_grow_and_shrink_defaults_match_css_initial_values() {
1180 assert_eq!(LayoutFlexGrow::default().inner.get(), 0.0);
1181 assert_eq!(LayoutFlexShrink::default().inner.get(), 1.0);
1182 assert!(LayoutFlexGrow::const_new(1) < LayoutFlexGrow::const_new(2));
1184 assert!(LayoutFlexGrow::const_new(-1) < LayoutFlexGrow::const_new(0));
1185 }
1186
1187 fn grow(v: f32) -> LayoutFlexGrow {
1192 LayoutFlexGrow {
1193 inner: FloatValue::new(v),
1194 }
1195 }
1196
1197 fn shrink(v: f32) -> LayoutFlexShrink {
1198 LayoutFlexShrink {
1199 inner: FloatValue::new(v),
1200 }
1201 }
1202
1203 #[test]
1204 fn flex_grow_interpolate_endpoints_midpoint_and_extrapolation() {
1205 let a = grow(0.0);
1206 let b = grow(10.0);
1207
1208 assert_eq!(a.interpolate(&b, 0.0).inner.get(), 0.0);
1209 assert_eq!(a.interpolate(&b, 1.0).inner.get(), 10.0);
1210 assert_eq!(a.interpolate(&b, 0.5).inner.get(), 5.0);
1211 assert_eq!(a.interpolate(&b, 2.0).inner.get(), 20.0);
1213 assert_eq!(a.interpolate(&b, -1.0).inner.get(), -10.0);
1214 }
1215
1216 #[test]
1219 fn flex_grow_interpolate_nan_t_collapses_to_zero() {
1220 let a = grow(2.0);
1221 let b = grow(8.0);
1222
1223 let out = a.interpolate(&b, f32::NAN);
1224 assert!(out.inner.get().is_finite(), "NaN must not leak into the value");
1225 assert_eq!(out.inner.get(), 0.0);
1226 assert_eq!(out.inner.number(), 0);
1227 }
1228
1229 #[test]
1232 fn flex_grow_interpolate_infinite_t_saturates_finite() {
1233 let a = grow(0.0);
1234 let b = grow(10.0);
1235
1236 let pos = a.interpolate(&b, f32::INFINITY);
1237 assert!(pos.inner.get().is_finite());
1238 assert!(pos.inner.get() > 0.0);
1239 assert_eq!(pos.inner.number(), isize::MAX);
1240
1241 let neg = a.interpolate(&b, f32::NEG_INFINITY);
1242 assert!(neg.inner.get().is_finite());
1243 assert!(neg.inner.get() < 0.0);
1244 assert_eq!(neg.inner.number(), isize::MIN);
1245
1246 let same = grow(4.0).interpolate(&grow(4.0), f32::INFINITY);
1248 assert!(same.inner.get().is_finite());
1249 assert_eq!(same.inner.get(), 0.0);
1250 }
1251
1252 #[test]
1255 fn flex_grow_interpolate_extreme_endpoints_stay_finite() {
1256 let a = LayoutFlexGrow::new(isize::MAX);
1257 let b = LayoutFlexGrow::new(isize::MIN);
1258
1259 for t in [
1260 0.0_f32,
1261 0.5,
1262 1.0,
1263 -1.0,
1264 1e30,
1265 -1e30,
1266 f32::MIN_POSITIVE,
1267 f32::NAN,
1268 f32::INFINITY,
1269 f32::NEG_INFINITY,
1270 ] {
1271 let out = a.interpolate(&b, t);
1272 assert!(
1273 out.inner.get().is_finite(),
1274 "interpolate(MAX, MIN, {t}) produced a non-finite value"
1275 );
1276 }
1277
1278 assert_eq!(a.interpolate(&b, 0.0).inner.get(), a.inner.get());
1280 }
1281
1282 #[test]
1283 fn flex_shrink_interpolate_endpoints_nan_and_inf() {
1284 let a = shrink(1.0);
1285 let b = shrink(3.0);
1286
1287 assert_eq!(a.interpolate(&b, 0.0).inner.get(), 1.0);
1288 assert_eq!(a.interpolate(&b, 1.0).inner.get(), 3.0);
1289 assert_eq!(a.interpolate(&b, 0.5).inner.get(), 2.0);
1290 assert_eq!(a.interpolate(&b, -2.0).inner.get(), -3.0);
1291
1292 assert_eq!(a.interpolate(&b, f32::NAN).inner.get(), 0.0);
1293 assert!(a.interpolate(&b, f32::INFINITY).inner.get().is_finite());
1294 assert!(a.interpolate(&b, f32::NEG_INFINITY).inner.get().is_finite());
1295
1296 let extreme = LayoutFlexShrink {
1297 inner: FloatValue::new(f32::MAX),
1298 };
1299 assert!(extreme.interpolate(&a, 0.5).inner.get().is_finite());
1300 assert!(extreme.interpolate(&a, f32::NAN).inner.get().is_finite());
1301 }
1302
1303 const ALL_DIRECTIONS: [LayoutFlexDirection; 4] = [
1308 LayoutFlexDirection::Row,
1309 LayoutFlexDirection::RowReverse,
1310 LayoutFlexDirection::Column,
1311 LayoutFlexDirection::ColumnReverse,
1312 ];
1313
1314 #[test]
1315 fn flex_direction_get_axis_is_exhaustive_and_reverse_invariant() {
1316 assert_eq!(LayoutFlexDirection::Row.get_axis(), LayoutAxis::Horizontal);
1317 assert_eq!(
1318 LayoutFlexDirection::RowReverse.get_axis(),
1319 LayoutAxis::Horizontal
1320 );
1321 assert_eq!(LayoutFlexDirection::Column.get_axis(), LayoutAxis::Vertical);
1322 assert_eq!(
1323 LayoutFlexDirection::ColumnReverse.get_axis(),
1324 LayoutAxis::Vertical
1325 );
1326
1327 assert_eq!(
1329 LayoutFlexDirection::Row.get_axis(),
1330 LayoutFlexDirection::RowReverse.get_axis()
1331 );
1332 assert_eq!(
1333 LayoutFlexDirection::Column.get_axis(),
1334 LayoutFlexDirection::ColumnReverse.get_axis()
1335 );
1336 assert_eq!(LayoutFlexDirection::default().get_axis(), LayoutAxis::Horizontal);
1338 assert!(!LayoutFlexDirection::default().is_reverse());
1339 }
1340
1341 #[test]
1342 fn flex_direction_is_reverse_exhaustive() {
1343 assert!(!LayoutFlexDirection::Row.is_reverse());
1344 assert!(LayoutFlexDirection::RowReverse.is_reverse());
1345 assert!(!LayoutFlexDirection::Column.is_reverse());
1346 assert!(LayoutFlexDirection::ColumnReverse.is_reverse());
1347
1348 let reversed = ALL_DIRECTIONS.iter().filter(|d| d.is_reverse()).count();
1350 assert_eq!(reversed, 2);
1351 for axis in [LayoutAxis::Horizontal, LayoutAxis::Vertical] {
1353 assert_eq!(
1354 ALL_DIRECTIONS
1355 .iter()
1356 .filter(|d| d.get_axis() == axis && d.is_reverse())
1357 .count(),
1358 1
1359 );
1360 }
1361 }
1362
1363 #[cfg(feature = "parser")]
1370 #[test]
1371 fn flex_grow_parse_empty_and_whitespace_only_are_err() {
1372 for input in ["", " ", " ", "\t", "\n", "\r\n", "\t \n "] {
1373 assert!(
1374 parse_layout_flex_grow(input).is_err(),
1375 "empty/whitespace input {input:?} must not parse"
1376 );
1377 assert!(parse_layout_flex_shrink(input).is_err());
1378 }
1379 }
1380
1381 #[cfg(feature = "parser")]
1382 #[test]
1383 fn flex_grow_parse_garbage_and_unicode_never_panics() {
1384 let deep_nesting = "(".repeat(10_000) + &")".repeat(10_000);
1385 let long_junk = "x".repeat(1_000_000);
1386 let long_digits = "9".repeat(100_000);
1387
1388 let garbage = [
1389 "none",
1390 "auto",
1391 "null",
1392 "1/2",
1393 "0x10",
1394 "1,0",
1395 "--1",
1396 "1-",
1397 "+-1",
1398 "1e",
1399 "e1",
1400 "\u{1F600}", "1\u{1F600}", "e\u{0301}", "\u{0661}\u{0662}", "1", "1\u{0}", "\u{200B}1", deep_nesting.as_str(),
1408 long_junk.as_str(),
1409 ];
1410
1411 for input in garbage {
1412 assert!(
1413 parse_layout_flex_grow(input).is_err(),
1414 "garbage input {:?} must be rejected",
1415 input.chars().take(8).collect::<String>()
1416 );
1417 assert!(parse_layout_flex_shrink(input).is_err());
1418 }
1419
1420 let huge = parse_layout_flex_grow(&long_digits).expect("huge finite-overflow input");
1422 assert!(huge.inner.get().is_finite());
1423 assert!(huge.inner.get() >= 0.0);
1424 }
1425
1426 #[cfg(feature = "parser")]
1427 #[test]
1428 fn flex_grow_parse_boundary_numbers() {
1429 assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
1431 assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
1432 assert_eq!(parse_layout_flex_grow("+2.5").unwrap().inner.get(), 2.5);
1433 assert_eq!(parse_layout_flex_grow(".5").unwrap().inner.get(), 0.5);
1434
1435 let neg_zero = parse_layout_flex_grow("-0").unwrap();
1437 assert_eq!(neg_zero.inner.get(), 0.0);
1438 assert_eq!(neg_zero.inner.number(), 0);
1439
1440 assert!(matches!(
1442 parse_layout_flex_grow("-1"),
1443 Err(FlexGrowParseError::NegativeValue("-1"))
1444 ));
1445 assert!(parse_layout_flex_grow("-0.01").is_err());
1446 assert!(parse_layout_flex_grow("-1e10").is_err());
1447
1448 assert!(matches!(
1450 parse_layout_flex_grow("-inf"),
1451 Err(FlexGrowParseError::NegativeValue(_))
1452 ));
1453
1454 let tiny_neg = parse_layout_flex_grow("-0.0001").unwrap();
1457 assert_eq!(tiny_neg.inner.get(), 0.0);
1458 assert_eq!(parse_layout_flex_grow("-1e-30").unwrap().inner.get(), 0.0);
1459
1460 for input in ["inf", "1e40", "9223372036854775807", "3.5e38"] {
1462 let parsed = parse_layout_flex_grow(input)
1463 .unwrap_or_else(|e| panic!("{input:?} unexpectedly rejected: {e}"));
1464 assert!(
1465 parsed.inner.get().is_finite(),
1466 "{input:?} decoded to a non-finite value"
1467 );
1468 assert!(parsed.inner.get() >= 0.0);
1469 }
1470
1471 assert_eq!(parse_layout_flex_grow("1e-40").unwrap().inner.get(), 0.0);
1473
1474 let nan = parse_layout_flex_grow("NaN").unwrap();
1477 assert!(nan.inner.get().is_finite());
1478 assert_eq!(nan.inner.get(), 0.0);
1479 }
1480
1481 #[cfg(feature = "parser")]
1482 #[test]
1483 fn flex_shrink_parse_boundary_numbers() {
1484 assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
1485 assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
1486 assert_eq!(parse_layout_flex_shrink("-0").unwrap().inner.get(), 0.0);
1487
1488 assert!(matches!(
1489 parse_layout_flex_shrink("-1"),
1490 Err(FlexShrinkParseError::NegativeValue("-1"))
1491 ));
1492 assert!(parse_layout_flex_shrink("-inf").is_err());
1493
1494 let huge = parse_layout_flex_shrink("1e40").unwrap();
1495 assert!(huge.inner.get().is_finite() && huge.inner.get() > 0.0);
1496 assert_eq!(parse_layout_flex_shrink("NaN").unwrap().inner.get(), 0.0);
1497 }
1498
1499 #[cfg(feature = "parser")]
1500 #[test]
1501 fn flex_grow_parse_leading_trailing_junk() {
1502 assert_eq!(parse_layout_flex_grow(" 0.5 ").unwrap().inner.get(), 0.5);
1504 assert_eq!(parse_layout_flex_grow("\t2\n").unwrap().inner.get(), 2.0);
1505
1506 for input in ["1;", "1 2", "1px", "1%", "valid;garbage", "1 1 1"] {
1508 assert!(
1509 parse_layout_flex_grow(input).is_err(),
1510 "{input:?} must be rejected"
1511 );
1512 }
1513 }
1514
1515 #[cfg(feature = "parser")]
1518 #[test]
1519 fn flex_grow_error_preserves_untrimmed_input() {
1520 match parse_layout_flex_grow(" bogus ") {
1521 Err(FlexGrowParseError::ParseFloat(_, s)) => assert_eq!(s, " bogus "),
1522 other => panic!("expected ParseFloat error, got {other:?}"),
1523 }
1524 match parse_layout_flex_shrink(" -2 ") {
1525 Err(FlexShrinkParseError::NegativeValue(s)) => assert_eq!(s, " -2 "),
1526 other => panic!("expected NegativeValue error, got {other:?}"),
1527 }
1528 }
1529
1530 #[cfg(feature = "parser")]
1533 #[test]
1534 fn flex_grow_shrink_print_parse_round_trip() {
1535 for v in [0.0_f32, 1.0, 2.5, 0.25, 0.125, 100.0, 12.5] {
1536 let g = grow(v);
1537 let printed = g.print_as_css_value();
1538 assert_eq!(
1539 parse_layout_flex_grow(&printed).unwrap().inner.number(),
1540 g.inner.number(),
1541 "flex-grow round-trip failed for {printed}"
1542 );
1543
1544 let s = shrink(v);
1545 let printed = s.print_as_css_value();
1546 assert_eq!(
1547 parse_layout_flex_shrink(&printed).unwrap().inner.number(),
1548 s.inner.number(),
1549 "flex-shrink round-trip failed for {printed}"
1550 );
1551 }
1552 }
1553
1554 #[cfg(feature = "parser")]
1558 #[test]
1559 fn keyword_enums_print_parse_round_trip() {
1560 for d in ALL_DIRECTIONS {
1561 assert_eq!(parse_layout_flex_direction(&d.print_as_css_value()).unwrap(), d);
1562 }
1563 for w in [
1564 LayoutFlexWrap::Wrap,
1565 LayoutFlexWrap::NoWrap,
1566 LayoutFlexWrap::WrapReverse,
1567 ] {
1568 assert_eq!(parse_layout_flex_wrap(&w.print_as_css_value()).unwrap(), w);
1569 }
1570 for j in [
1571 LayoutJustifyContent::FlexStart,
1572 LayoutJustifyContent::FlexEnd,
1573 LayoutJustifyContent::Start,
1574 LayoutJustifyContent::End,
1575 LayoutJustifyContent::Center,
1576 LayoutJustifyContent::SpaceBetween,
1577 LayoutJustifyContent::SpaceAround,
1578 LayoutJustifyContent::SpaceEvenly,
1579 ] {
1580 assert_eq!(parse_layout_justify_content(&j.print_as_css_value()).unwrap(), j);
1581 }
1582 for a in [
1583 LayoutAlignItems::Stretch,
1584 LayoutAlignItems::Center,
1585 LayoutAlignItems::Start,
1586 LayoutAlignItems::End,
1587 LayoutAlignItems::Baseline,
1588 ] {
1589 assert_eq!(parse_layout_align_items(&a.print_as_css_value()).unwrap(), a);
1590 }
1591 for a in [
1592 LayoutAlignContent::Stretch,
1593 LayoutAlignContent::Center,
1594 LayoutAlignContent::Start,
1595 LayoutAlignContent::End,
1596 LayoutAlignContent::SpaceBetween,
1597 LayoutAlignContent::SpaceAround,
1598 ] {
1599 assert_eq!(parse_layout_align_content(&a.print_as_css_value()).unwrap(), a);
1600 }
1601 for a in [
1602 LayoutAlignSelf::Auto,
1603 LayoutAlignSelf::Stretch,
1604 LayoutAlignSelf::Center,
1605 LayoutAlignSelf::Start,
1606 LayoutAlignSelf::End,
1607 LayoutAlignSelf::Baseline,
1608 ] {
1609 assert_eq!(parse_layout_align_self(&a.print_as_css_value()).unwrap(), a);
1610 }
1611 }
1612
1613 #[cfg(feature = "parser")]
1614 #[test]
1615 fn keyword_parsers_reject_empty_whitespace_and_garbage() {
1616 let deep_nesting = "[".repeat(10_000) + &"]".repeat(10_000);
1617 let long_junk = "row".repeat(300_000); let bad = [
1619 "",
1620 " ",
1621 "\t\n",
1622 "0",
1623 "-1",
1624 "NaN",
1625 "inf",
1626 "9223372036854775807",
1627 "\u{1F600}",
1628 "row\u{200B}", "row\u{0}", "row row",
1631 "row;",
1632 ";row",
1633 "row/**/",
1634 deep_nesting.as_str(),
1635 long_junk.as_str(),
1636 ];
1637
1638 for input in bad {
1639 assert!(parse_layout_flex_direction(input).is_err());
1640 assert!(parse_layout_flex_wrap(input).is_err());
1641 assert!(parse_layout_justify_content(input).is_err());
1642 assert!(parse_layout_align_items(input).is_err());
1643 assert!(parse_layout_align_content(input).is_err());
1644 assert!(parse_layout_align_self(input).is_err());
1645 }
1646 }
1647
1648 #[cfg(feature = "parser")]
1651 #[test]
1652 fn keyword_parsers_are_case_sensitive() {
1653 assert!(parse_layout_flex_direction("ROW").is_err());
1654 assert!(parse_layout_flex_direction("Row").is_err());
1655 assert!(parse_layout_flex_wrap("NoWrap").is_err());
1656 assert!(parse_layout_justify_content("Center").is_err());
1657 assert!(parse_layout_align_items("STRETCH").is_err());
1658 assert!(parse_layout_align_content("Stretch").is_err());
1659 assert!(parse_layout_align_self("AUTO").is_err());
1660
1661 assert_eq!(
1663 parse_layout_flex_direction("row").unwrap(),
1664 LayoutFlexDirection::Row
1665 );
1666 assert_eq!(parse_layout_align_self("auto").unwrap(), LayoutAlignSelf::Auto);
1667 }
1668
1669 #[cfg(feature = "parser")]
1671 #[test]
1672 fn keyword_errors_preserve_untrimmed_input() {
1673 assert_eq!(
1674 parse_layout_flex_direction(" bogus ").unwrap_err(),
1675 FlexDirectionParseError::InvalidValue(" bogus ")
1676 );
1677 assert_eq!(
1678 parse_layout_flex_wrap("\twrap!\n").unwrap_err(),
1679 FlexWrapParseError::InvalidValue("\twrap!\n")
1680 );
1681 assert_eq!(
1682 parse_layout_justify_content("").unwrap_err(),
1683 JustifyContentParseError::InvalidValue("")
1684 );
1685 assert_eq!(
1686 parse_layout_align_items(" nope ").unwrap_err(),
1687 AlignItemsParseError::InvalidValue(" nope ")
1688 );
1689 assert_eq!(
1690 parse_layout_align_content(" nope ").unwrap_err(),
1691 AlignContentParseError::InvalidValue(" nope ")
1692 );
1693 assert_eq!(
1694 parse_layout_align_self(" nope ").unwrap_err(),
1695 AlignSelfParseError::InvalidValue(" nope ")
1696 );
1697 }
1698
1699 #[cfg(feature = "parser")]
1702 #[test]
1703 fn align_aliases_collapse_but_justify_keeps_them_distinct() {
1704 assert_eq!(
1705 parse_layout_align_items("start").unwrap(),
1706 parse_layout_align_items("flex-start").unwrap()
1707 );
1708 assert_eq!(
1709 parse_layout_align_content("end").unwrap(),
1710 parse_layout_align_content("flex-end").unwrap()
1711 );
1712 assert_eq!(
1713 parse_layout_align_self("start").unwrap(),
1714 parse_layout_align_self("flex-start").unwrap()
1715 );
1716 assert_ne!(
1717 parse_layout_justify_content("start").unwrap(),
1718 parse_layout_justify_content("flex-start").unwrap()
1719 );
1720 assert!(parse_layout_justify_content("space-evenly").is_ok());
1722 assert!(parse_layout_align_content("space-evenly").is_err());
1723 assert!(parse_layout_align_self("auto").is_ok());
1725 assert!(parse_layout_align_items("auto").is_err());
1726 }
1727
1728 #[cfg(feature = "parser")]
1731 #[test]
1732 fn flex_basis_print_parse_round_trip() {
1733 for basis in [
1734 LayoutFlexBasis::Auto,
1735 LayoutFlexBasis::Exact(PixelValue::px(0.0)),
1736 LayoutFlexBasis::Exact(PixelValue::px(200.0)),
1737 LayoutFlexBasis::Exact(PixelValue::px(-5.0)),
1738 LayoutFlexBasis::Exact(PixelValue::percent(50.0)),
1739 LayoutFlexBasis::Exact(PixelValue::em(10.5)),
1740 LayoutFlexBasis::Exact(PixelValue::rem(1.25)),
1741 LayoutFlexBasis::Exact(PixelValue::pt(12.0)),
1742 ] {
1743 let printed = basis.print_as_css_value();
1744 assert_eq!(
1745 parse_layout_flex_basis(&printed).unwrap(),
1746 basis,
1747 "flex-basis round-trip failed for {printed}"
1748 );
1749 }
1750 }
1751
1752 #[cfg(feature = "parser")]
1753 #[test]
1754 fn flex_basis_rejects_empty_units_only_and_garbage() {
1755 let deep_nesting = "(".repeat(10_000) + &")".repeat(10_000);
1756 let long_junk = "z".repeat(1_000_000);
1757
1758 for input in [
1759 "",
1760 " ",
1761 "\t\n",
1762 "px", "%",
1764 "em",
1765 " px ",
1766 "none",
1767 "auto auto",
1768 "200px;",
1769 "200 px extra",
1770 "AUTO", "5PX",
1772 "\u{1F600}",
1773 "50px", "200\u{200B}px",
1775 deep_nesting.as_str(),
1776 long_junk.as_str(),
1777 ] {
1778 assert!(
1779 parse_layout_flex_basis(input).is_err(),
1780 "flex-basis {:?} must be rejected",
1781 input.chars().take(10).collect::<String>()
1782 );
1783 }
1784
1785 assert_eq!(
1787 parse_layout_flex_basis(" none ").unwrap_err(),
1788 FlexBasisParseError::InvalidValue(" none ")
1789 );
1790 }
1791
1792 #[cfg(feature = "parser")]
1795 #[test]
1796 fn flex_basis_nan_and_inf_units_saturate() {
1797 assert_eq!(
1799 parse_layout_flex_basis("NaNpx").unwrap(),
1800 LayoutFlexBasis::Exact(PixelValue::px(0.0))
1801 );
1802 assert_eq!(
1804 parse_layout_flex_basis("infpx").unwrap(),
1805 LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
1806 );
1807 assert_eq!(
1808 parse_layout_flex_basis("1e40px").unwrap(),
1809 LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
1810 );
1811 assert_eq!(
1812 parse_layout_flex_basis(&"9".repeat(100_000)).unwrap(),
1813 LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
1814 );
1815
1816 assert_eq!(
1818 parse_layout_flex_basis("-0").unwrap(),
1819 LayoutFlexBasis::Exact(PixelValue::px(0.0))
1820 );
1821 assert_eq!(
1823 parse_layout_flex_basis("-5px").unwrap(),
1824 LayoutFlexBasis::Exact(PixelValue::px(-5.0))
1825 );
1826 assert_eq!(
1828 parse_layout_flex_basis("5 px").unwrap(),
1829 LayoutFlexBasis::Exact(PixelValue::px(5.0))
1830 );
1831 }
1832
1833 #[cfg(feature = "parser")]
1840 #[test]
1841 fn flex_grow_shrink_error_owned_round_trip() {
1842 let invalid = "x".parse::<f32>().unwrap_err();
1843 let empty = "".parse::<f32>().unwrap_err();
1844 let long = "q".repeat(10_000);
1845
1846 for payload in ["", "abc", "\u{1F600}\u{0301}", " spaced ", long.as_str()] {
1847 for err in [
1848 FlexGrowParseError::ParseFloat(invalid.clone(), payload),
1849 FlexGrowParseError::ParseFloat(empty.clone(), payload),
1850 FlexGrowParseError::NegativeValue(payload),
1851 ] {
1852 assert_eq!(err.to_contained().to_shared(), err);
1853 }
1854 for err in [
1855 FlexShrinkParseError::ParseFloat(invalid.clone(), payload),
1856 FlexShrinkParseError::NegativeValue(payload),
1857 ] {
1858 assert_eq!(err.to_contained().to_shared(), err);
1859 }
1860 }
1861 }
1862
1863 #[cfg(feature = "parser")]
1864 #[test]
1865 fn keyword_error_owned_round_trip() {
1866 let long = "k".repeat(10_000);
1867
1868 for payload in ["", " ", "\u{1F600}", "bogus", long.as_str()] {
1869 let d = FlexDirectionParseError::InvalidValue(payload);
1870 assert_eq!(d.to_contained().to_shared(), d);
1871
1872 let w = FlexWrapParseError::InvalidValue(payload);
1873 assert_eq!(w.to_contained().to_shared(), w);
1874
1875 let j = JustifyContentParseError::InvalidValue(payload);
1876 assert_eq!(j.to_contained().to_shared(), j);
1877
1878 let ai = AlignItemsParseError::InvalidValue(payload);
1879 assert_eq!(ai.to_contained().to_shared(), ai);
1880
1881 let ac = AlignContentParseError::InvalidValue(payload);
1882 assert_eq!(ac.to_contained().to_shared(), ac);
1883
1884 let asf = AlignSelfParseError::InvalidValue(payload);
1885 assert_eq!(asf.to_contained().to_shared(), asf);
1886
1887 let b = FlexBasisParseError::InvalidValue(payload);
1888 assert_eq!(b.to_contained().to_shared(), b);
1889 }
1890 }
1891
1892 #[cfg(feature = "parser")]
1895 #[test]
1896 fn owned_errors_to_shared_on_degenerate_payloads() {
1897 let empty: AzString = String::new().into();
1898
1899 assert_eq!(
1900 FlexDirectionParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1901 FlexDirectionParseError::InvalidValue("")
1902 );
1903 assert_eq!(
1904 FlexWrapParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1905 FlexWrapParseError::InvalidValue("")
1906 );
1907 assert_eq!(
1908 JustifyContentParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1909 JustifyContentParseError::InvalidValue("")
1910 );
1911 assert_eq!(
1912 AlignItemsParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1913 AlignItemsParseError::InvalidValue("")
1914 );
1915 assert_eq!(
1916 AlignContentParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1917 AlignContentParseError::InvalidValue("")
1918 );
1919 assert_eq!(
1920 AlignSelfParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1921 AlignSelfParseError::InvalidValue("")
1922 );
1923 assert_eq!(
1924 FlexBasisParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
1925 FlexBasisParseError::InvalidValue("")
1926 );
1927 assert_eq!(
1928 FlexGrowParseErrorOwned::NegativeValue(empty.clone()).to_shared(),
1929 FlexGrowParseError::NegativeValue("")
1930 );
1931 assert_eq!(
1932 FlexShrinkParseErrorOwned::NegativeValue(empty).to_shared(),
1933 FlexShrinkParseError::NegativeValue("")
1934 );
1935 }
1936
1937 #[cfg(feature = "parser")]
1940 #[test]
1941 fn parser_errors_to_contained_and_display() {
1942 let e = parse_layout_flex_grow("bogus").unwrap_err();
1943 assert_eq!(e.to_contained().to_shared(), e);
1944 assert!(format!("{e}").contains("flex-grow"));
1945
1946 let e = parse_layout_flex_shrink("-1").unwrap_err();
1947 assert_eq!(e.to_contained().to_shared(), e);
1948 assert!(format!("{e}").contains("flex-shrink"));
1949
1950 let e = parse_layout_flex_direction("\u{1F600}").unwrap_err();
1951 assert_eq!(e.to_contained().to_shared(), e);
1952 assert!(format!("{e}").contains("flex-direction"));
1953
1954 let e = parse_layout_flex_wrap("").unwrap_err();
1955 assert_eq!(e.to_contained().to_shared(), e);
1956 assert!(format!("{e}").contains("flex-wrap"));
1957
1958 let e = parse_layout_justify_content("nope").unwrap_err();
1959 assert_eq!(e.to_contained().to_shared(), e);
1960 assert!(format!("{e}").contains("justify-content"));
1961
1962 let e = parse_layout_align_items("nope").unwrap_err();
1963 assert_eq!(e.to_contained().to_shared(), e);
1964 assert!(format!("{e}").contains("align-items"));
1965
1966 let e = parse_layout_align_content("nope").unwrap_err();
1967 assert_eq!(e.to_contained().to_shared(), e);
1968 assert!(format!("{e}").contains("align-content"));
1969
1970 let e = parse_layout_align_self("nope").unwrap_err();
1971 assert_eq!(e.to_contained().to_shared(), e);
1972 assert!(format!("{e}").contains("align-self"));
1973
1974 let e = parse_layout_flex_basis("none").unwrap_err();
1975 assert_eq!(e.to_contained().to_shared(), e);
1976 assert!(format!("{e}").contains("flex-basis"));
1977 }
1978}