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