1
//! CSS properties for flexbox layout.
2

            
3
use alloc::string::{String, ToString};
4
use core::num::ParseFloatError;
5
use crate::corety::AzString;
6

            
7
use 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
// --- flex-grow ---
19

            
20
/// Represents a `flex-grow` attribute, which dictates what proportion of the
21
/// remaining space in the flex container should be assigned to the item.
22
/// Default: 0
23
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24
#[repr(C)]
25
pub struct LayoutFlexGrow {
26
    pub inner: FloatValue,
27
}
28

            
29
impl core::fmt::Debug for LayoutFlexGrow {
30
408
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31
408
        write!(f, "{}", self.inner.get())
32
408
    }
33
}
34

            
35
impl Default for LayoutFlexGrow {
36
1
    fn default() -> Self {
37
1
        Self {
38
1
            inner: FloatValue::const_new(0),
39
1
        }
40
1
    }
41
}
42

            
43
impl PrintAsCssValue for LayoutFlexGrow {
44
7
    fn print_as_css_value(&self) -> String {
45
7
        format!("{}", self.inner)
46
7
    }
47
}
48

            
49
impl LayoutFlexGrow {
50
23
    #[must_use] pub fn new(value: isize) -> Self {
51
23
        Self {
52
23
            inner: FloatValue::new(crate::cast::isize_to_f32(value)),
53
23
        }
54
23
    }
55

            
56
232073
    #[must_use] pub const fn const_new(value: isize) -> Self {
57
232073
        Self {
58
232073
            inner: FloatValue::const_new(value),
59
232073
        }
60
232073
    }
61

            
62
20
    #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
63
20
        Self {
64
20
            inner: self.inner.interpolate(&other.inner, t),
65
20
        }
66
20
    }
67
}
68

            
69
#[cfg(feature = "parser")]
70
#[derive(Clone, PartialEq, Eq)]
71
pub enum FlexGrowParseError<'a> {
72
    ParseFloat(ParseFloatError, &'a str),
73
    NegativeValue(&'a str),
74
}
75

            
76
#[cfg(feature = "parser")]
77
impl_debug_as_display!(FlexGrowParseError<'a>);
78
#[cfg(feature = "parser")]
79
impl_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)]
87
pub enum FlexGrowParseErrorOwned {
88
    ParseFloat(ParseFloatErrorWithInput),
89
    NegativeValue(AzString),
90
}
91

            
92
#[cfg(feature = "parser")]
93
impl FlexGrowParseError<'_> {
94
16
    #[must_use] pub fn to_contained(&self) -> FlexGrowParseErrorOwned {
95
16
        match self {
96
11
            FlexGrowParseError::ParseFloat(e, s) => {
97
11
                FlexGrowParseErrorOwned::ParseFloat(ParseFloatErrorWithInput { error: e.clone().into(), input: (*s).to_string().into() })
98
            }
99
5
            FlexGrowParseError::NegativeValue(s) => {
100
5
                FlexGrowParseErrorOwned::NegativeValue((*s).to_string().into())
101
            }
102
        }
103
16
    }
104
}
105

            
106
#[cfg(feature = "parser")]
107
impl FlexGrowParseErrorOwned {
108
17
    #[must_use] pub fn to_shared(&self) -> FlexGrowParseError<'_> {
109
17
        match self {
110
11
            Self::ParseFloat(e) => {
111
11
                FlexGrowParseError::ParseFloat(e.error.to_std(), e.input.as_str())
112
            }
113
6
            Self::NegativeValue(s) => {
114
6
                FlexGrowParseError::NegativeValue(s.as_str())
115
            }
116
        }
117
17
    }
118
}
119

            
120
#[cfg(feature = "parser")]
121
/// # Errors
122
///
123
/// Returns an error if `input` is not a valid CSS `flex-grow` value.
124
1810
pub fn parse_layout_flex_grow(
125
1810
    input: &str,
126
1810
) -> Result<LayoutFlexGrow, FlexGrowParseError<'_>> {
127
1810
    match parse_float_value(input) {
128
1774
        Ok(o) => {
129
1774
            if o.get() < 0.0 {
130
5
                Err(FlexGrowParseError::NegativeValue(input))
131
            } else {
132
1769
                Ok(LayoutFlexGrow { inner: o })
133
            }
134
        }
135
36
        Err(e) => Err(FlexGrowParseError::ParseFloat(e, input)),
136
    }
137
1810
}
138

            
139
// --- flex-shrink ---
140

            
141
/// Represents a `flex-shrink` attribute, which dictates what proportion of
142
/// the negative space in the flex container should be removed from the item.
143
/// Default: 1
144
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
145
#[repr(C)]
146
pub struct LayoutFlexShrink {
147
    pub inner: FloatValue,
148
}
149

            
150
impl 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

            
156
impl Default for LayoutFlexShrink {
157
1
    fn default() -> Self {
158
1
        Self {
159
1
            inner: FloatValue::const_new(1),
160
1
        }
161
1
    }
162
}
163

            
164
impl PrintAsCssValue for LayoutFlexShrink {
165
7
    fn print_as_css_value(&self) -> String {
166
7
        format!("{}", self.inner)
167
7
    }
168
}
169

            
170
impl LayoutFlexShrink {
171
9
    #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
172
9
        Self {
173
9
            inner: self.inner.interpolate(&other.inner, t),
174
9
        }
175
9
    }
176
}
177

            
178
#[cfg(feature = "parser")]
179
#[derive(Clone, PartialEq, Eq)]
180
pub enum FlexShrinkParseError<'a> {
181
    ParseFloat(ParseFloatError, &'a str),
182
    NegativeValue(&'a str),
183
}
184

            
185
#[cfg(feature = "parser")]
186
impl_debug_as_display!(FlexShrinkParseError<'a>);
187
#[cfg(feature = "parser")]
188
impl_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)]
196
pub enum FlexShrinkParseErrorOwned {
197
    ParseFloat(ParseFloatErrorWithInput),
198
    NegativeValue(AzString),
199
}
200

            
201
#[cfg(feature = "parser")]
202
impl FlexShrinkParseError<'_> {
203
11
    #[must_use] pub fn to_contained(&self) -> FlexShrinkParseErrorOwned {
204
11
        match self {
205
5
            FlexShrinkParseError::ParseFloat(e, s) => {
206
5
                FlexShrinkParseErrorOwned::ParseFloat(ParseFloatErrorWithInput { error: e.clone().into(), input: (*s).to_string().into() })
207
            }
208
6
            FlexShrinkParseError::NegativeValue(s) => {
209
6
                FlexShrinkParseErrorOwned::NegativeValue((*s).to_string().into())
210
            }
211
        }
212
11
    }
213
}
214

            
215
#[cfg(feature = "parser")]
216
impl FlexShrinkParseErrorOwned {
217
12
    #[must_use] pub fn to_shared(&self) -> FlexShrinkParseError<'_> {
218
12
        match self {
219
5
            Self::ParseFloat(e) => {
220
5
                FlexShrinkParseError::ParseFloat(e.error.to_std(), e.input.as_str())
221
            }
222
7
            Self::NegativeValue(s) => {
223
7
                FlexShrinkParseError::NegativeValue(s.as_str())
224
            }
225
        }
226
12
    }
227
}
228

            
229
#[cfg(feature = "parser")]
230
/// # Errors
231
///
232
/// Returns an error if `input` is not a valid CSS `flex-shrink` value.
233
461
pub fn parse_layout_flex_shrink(
234
461
    input: &str,
235
461
) -> Result<LayoutFlexShrink, FlexShrinkParseError<'_>> {
236
461
    match parse_float_value(input) {
237
426
        Ok(o) => {
238
426
            if o.get() < 0.0 {
239
5
                Err(FlexShrinkParseError::NegativeValue(input))
240
            } else {
241
421
                Ok(LayoutFlexShrink { inner: o })
242
            }
243
        }
244
35
        Err(e) => Err(FlexShrinkParseError::ParseFloat(e, input)),
245
    }
246
461
}
247

            
248
// --- flex-direction ---
249

            
250
/// Represents a `flex-direction` attribute, which establishes the main-axis,
251
/// thus defining the direction flex items are placed in the flex container.
252
/// Default: `Row`
253
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254
#[repr(C)]
255
#[derive(Default)]
256
pub enum LayoutFlexDirection {
257
    #[default]
258
    Row,
259
    RowReverse,
260
    Column,
261
    ColumnReverse,
262
}
263

            
264

            
265
/// Represents the main or cross axis of a flex container.
266
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
267
#[repr(C)]
268
pub enum LayoutAxis {
269
    Horizontal,
270
    Vertical,
271
}
272

            
273
impl LayoutFlexDirection {
274
17
    #[must_use] pub const fn get_axis(&self) -> LayoutAxis {
275
17
        match self {
276
9
            Self::Row | Self::RowReverse => LayoutAxis::Horizontal,
277
8
            Self::Column | Self::ColumnReverse => LayoutAxis::Vertical,
278
        }
279
17
    }
280

            
281
13
    #[must_use] pub const fn is_reverse(&self) -> bool {
282
13
        matches!(self, Self::RowReverse | Self::ColumnReverse)
283
13
    }
284
}
285

            
286
impl PrintAsCssValue for LayoutFlexDirection {
287
124
    fn print_as_css_value(&self) -> String {
288
124
        String::from(match self {
289
1
            Self::Row => "row",
290
1
            Self::RowReverse => "row-reverse",
291
121
            Self::Column => "column",
292
1
            Self::ColumnReverse => "column-reverse",
293
        })
294
124
    }
295
}
296

            
297
#[cfg(feature = "parser")]
298
#[derive(Clone, PartialEq, Eq)]
299
pub enum FlexDirectionParseError<'a> {
300
    InvalidValue(&'a str),
301
}
302

            
303
#[cfg(feature = "parser")]
304
impl_debug_as_display!(FlexDirectionParseError<'a>);
305
#[cfg(feature = "parser")]
306
impl_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)]
313
pub enum FlexDirectionParseErrorOwned {
314
    InvalidValue(AzString),
315
}
316

            
317
#[cfg(feature = "parser")]
318
impl FlexDirectionParseError<'_> {
319
6
    #[must_use] pub fn to_contained(&self) -> FlexDirectionParseErrorOwned {
320
6
        match self {
321
6
            Self::InvalidValue(s) => FlexDirectionParseErrorOwned::InvalidValue((*s).to_string().into()),
322
        }
323
6
    }
324
}
325

            
326
#[cfg(feature = "parser")]
327
impl FlexDirectionParseErrorOwned {
328
7
    #[must_use] pub fn to_shared(&self) -> FlexDirectionParseError<'_> {
329
7
        match self {
330
7
            Self::InvalidValue(s) => FlexDirectionParseError::InvalidValue(s.as_str()),
331
        }
332
7
    }
333
}
334

            
335
#[cfg(feature = "parser")]
336
/// # Errors
337
///
338
/// Returns an error if `input` is not a valid CSS `flex-direction` value.
339
19191
pub fn parse_layout_flex_direction(
340
19191
    input: &str,
341
19191
) -> Result<LayoutFlexDirection, FlexDirectionParseError<'_>> {
342
19191
    match input.trim() {
343
19191
        "row" => Ok(LayoutFlexDirection::Row),
344
1160
        "row-reverse" => Ok(LayoutFlexDirection::RowReverse),
345
1158
        "column" => Ok(LayoutFlexDirection::Column),
346
24
        "column-reverse" => Ok(LayoutFlexDirection::ColumnReverse),
347
22
        _ => Err(FlexDirectionParseError::InvalidValue(input)),
348
    }
349
19191
}
350

            
351
// --- flex-wrap ---
352

            
353
/// Represents a `flex-wrap` attribute, which determines whether flex items
354
/// are forced onto one line or can wrap onto multiple lines.
355
/// Default: `NoWrap`
356
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
357
#[repr(C)]
358
#[derive(Default)]
359
pub enum LayoutFlexWrap {
360
    Wrap,
361
    #[default]
362
    NoWrap,
363
    WrapReverse,
364
}
365

            
366

            
367
impl PrintAsCssValue for LayoutFlexWrap {
368
3
    fn print_as_css_value(&self) -> String {
369
3
        String::from(match self {
370
1
            Self::Wrap => "wrap",
371
1
            Self::NoWrap => "nowrap",
372
1
            Self::WrapReverse => "wrap-reverse",
373
        })
374
3
    }
375
}
376

            
377
#[cfg(feature = "parser")]
378
#[derive(Clone, PartialEq, Eq)]
379
pub enum FlexWrapParseError<'a> {
380
    InvalidValue(&'a str),
381
}
382

            
383
#[cfg(feature = "parser")]
384
impl_debug_as_display!(FlexWrapParseError<'a>);
385
#[cfg(feature = "parser")]
386
impl_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)]
393
pub enum FlexWrapParseErrorOwned {
394
    InvalidValue(AzString),
395
}
396

            
397
#[cfg(feature = "parser")]
398
impl FlexWrapParseError<'_> {
399
6
    #[must_use] pub fn to_contained(&self) -> FlexWrapParseErrorOwned {
400
6
        match self {
401
6
            Self::InvalidValue(s) => FlexWrapParseErrorOwned::InvalidValue((*s).to_string().into()),
402
        }
403
6
    }
404
}
405

            
406
#[cfg(feature = "parser")]
407
impl FlexWrapParseErrorOwned {
408
7
    #[must_use] pub fn to_shared(&self) -> FlexWrapParseError<'_> {
409
7
        match self {
410
7
            Self::InvalidValue(s) => FlexWrapParseError::InvalidValue(s.as_str()),
411
        }
412
7
    }
413
}
414

            
415
#[cfg(feature = "parser")]
416
/// # Errors
417
///
418
/// Returns an error if `input` is not a valid CSS `flex-wrap` value.
419
61
pub fn parse_layout_flex_wrap(
420
61
    input: &str,
421
61
) -> Result<LayoutFlexWrap, FlexWrapParseError<'_>> {
422
61
    match input.trim() {
423
61
        "wrap" => Ok(LayoutFlexWrap::Wrap),
424
25
        "nowrap" => Ok(LayoutFlexWrap::NoWrap),
425
23
        "wrap-reverse" => Ok(LayoutFlexWrap::WrapReverse),
426
21
        _ => Err(FlexWrapParseError::InvalidValue(input)),
427
    }
428
61
}
429

            
430
// --- justify-content ---
431

            
432
/// Represents a `justify-content` attribute, which defines the alignment
433
/// along the main axis.
434
/// Default: `Start` (flex-start)
435
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436
#[repr(C)]
437
#[derive(Default)]
438
pub enum LayoutJustifyContent {
439
    FlexStart,
440
    FlexEnd,
441
    #[default]
442
    Start,
443
    End,
444
    Center,
445
    SpaceBetween,
446
    SpaceAround,
447
    SpaceEvenly,
448
}
449

            
450

            
451
impl PrintAsCssValue for LayoutJustifyContent {
452
8
    fn print_as_css_value(&self) -> String {
453
8
        String::from(match self {
454
1
            Self::Start => "start",
455
1
            Self::End => "end",
456
1
            Self::FlexStart => "flex-start",
457
1
            Self::FlexEnd => "flex-end",
458
1
            Self::Center => "center",
459
1
            Self::SpaceBetween => "space-between",
460
1
            Self::SpaceAround => "space-around",
461
1
            Self::SpaceEvenly => "space-evenly",
462
        })
463
8
    }
464
}
465

            
466
#[cfg(feature = "parser")]
467
#[derive(Clone, PartialEq, Eq)]
468
pub enum JustifyContentParseError<'a> {
469
    InvalidValue(&'a str),
470
}
471

            
472
#[cfg(feature = "parser")]
473
impl_debug_as_display!(JustifyContentParseError<'a>);
474
#[cfg(feature = "parser")]
475
impl_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)]
482
pub enum JustifyContentParseErrorOwned {
483
    InvalidValue(AzString),
484
}
485

            
486
#[cfg(feature = "parser")]
487
impl JustifyContentParseError<'_> {
488
6
    #[must_use] pub fn to_contained(&self) -> JustifyContentParseErrorOwned {
489
6
        match self {
490
6
            Self::InvalidValue(s) => JustifyContentParseErrorOwned::InvalidValue((*s).to_string().into()),
491
        }
492
6
    }
493
}
494

            
495
#[cfg(feature = "parser")]
496
impl JustifyContentParseErrorOwned {
497
7
    #[must_use] pub fn to_shared(&self) -> JustifyContentParseError<'_> {
498
7
        match self {
499
7
            Self::InvalidValue(s) => JustifyContentParseError::InvalidValue(s.as_str()),
500
        }
501
7
    }
502
}
503

            
504
#[cfg(feature = "parser")]
505
/// # Errors
506
///
507
/// Returns an error if `input` is not a valid CSS `justify-content` value.
508
233
pub fn parse_layout_justify_content(
509
233
    input: &str,
510
233
) -> Result<LayoutJustifyContent, JustifyContentParseError<'_>> {
511
233
    match input.trim() {
512
233
        "flex-start" => Ok(LayoutJustifyContent::FlexStart),
513
230
        "flex-end" => Ok(LayoutJustifyContent::FlexEnd),
514
228
        "start" => Ok(LayoutJustifyContent::Start),
515
225
        "end" => Ok(LayoutJustifyContent::End),
516
223
        "center" => Ok(LayoutJustifyContent::Center),
517
151
        "space-between" => Ok(LayoutJustifyContent::SpaceBetween),
518
25
        "space-around" => Ok(LayoutJustifyContent::SpaceAround),
519
23
        "space-evenly" => Ok(LayoutJustifyContent::SpaceEvenly),
520
20
        _ => Err(JustifyContentParseError::InvalidValue(input)),
521
    }
522
233
}
523

            
524
// --- align-items ---
525

            
526
/// Represents an `align-items` attribute, which defines the default behavior for
527
/// how flex items are laid out along the cross axis on the current line.
528
/// Default: `Stretch`
529
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
530
#[repr(C)]
531
#[derive(Default)]
532
pub enum LayoutAlignItems {
533
    #[default]
534
    Stretch,
535
    Center,
536
    Start,
537
    End,
538
    Baseline,
539
}
540

            
541

            
542
impl PrintAsCssValue for LayoutAlignItems {
543
5
    fn print_as_css_value(&self) -> String {
544
5
        String::from(match self {
545
1
            Self::Stretch => "stretch",
546
1
            Self::Center => "center",
547
1
            Self::Start => "flex-start",
548
1
            Self::End => "flex-end",
549
1
            Self::Baseline => "baseline",
550
        })
551
5
    }
552
}
553

            
554
#[cfg(feature = "parser")]
555
#[derive(Clone, PartialEq, Eq)]
556
pub enum AlignItemsParseError<'a> {
557
    InvalidValue(&'a str),
558
}
559

            
560
#[cfg(feature = "parser")]
561
impl_debug_as_display!(AlignItemsParseError<'a>);
562
#[cfg(feature = "parser")]
563
impl_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)]
570
pub enum AlignItemsParseErrorOwned {
571
    InvalidValue(AzString),
572
}
573

            
574
#[cfg(feature = "parser")]
575
impl AlignItemsParseError<'_> {
576
6
    #[must_use] pub fn to_contained(&self) -> AlignItemsParseErrorOwned {
577
6
        match self {
578
6
            Self::InvalidValue(s) => AlignItemsParseErrorOwned::InvalidValue((*s).to_string().into()),
579
        }
580
6
    }
581
}
582

            
583
#[cfg(feature = "parser")]
584
impl AlignItemsParseErrorOwned {
585
7
    #[must_use] pub fn to_shared(&self) -> AlignItemsParseError<'_> {
586
7
        match self {
587
7
            Self::InvalidValue(s) => AlignItemsParseError::InvalidValue(s.as_str()),
588
        }
589
7
    }
590
}
591

            
592
#[cfg(feature = "parser")]
593
/// # Errors
594
///
595
/// Returns an error if `input` is not a valid CSS `align-items` value.
596
17137
pub fn parse_layout_align_items(
597
17137
    input: &str,
598
17137
) -> Result<LayoutAlignItems, AlignItemsParseError<'_>> {
599
17137
    match input.trim() {
600
17137
        "stretch" => Ok(LayoutAlignItems::Stretch),
601
16631
        "center" => Ok(LayoutAlignItems::Center),
602
44
        "start" | "flex-start" => Ok(LayoutAlignItems::Start),
603
27
        "end" | "flex-end" => Ok(LayoutAlignItems::End),
604
24
        "baseline" => Ok(LayoutAlignItems::Baseline),
605
22
        _ => Err(AlignItemsParseError::InvalidValue(input)),
606
    }
607
17137
}
608

            
609
// --- align-content ---
610

            
611
/// Represents an `align-content` attribute, which aligns a flex container's lines
612
/// within it when there is extra space in the cross-axis.
613
/// Default: `Stretch`
614
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
615
#[repr(C)]
616
#[derive(Default)]
617
pub enum LayoutAlignContent {
618
    #[default]
619
    Stretch,
620
    Center,
621
    Start,
622
    End,
623
    SpaceBetween,
624
    SpaceAround,
625
}
626

            
627

            
628
impl PrintAsCssValue for LayoutAlignContent {
629
6
    fn print_as_css_value(&self) -> String {
630
6
        String::from(match self {
631
1
            Self::Stretch => "stretch",
632
1
            Self::Center => "center",
633
1
            Self::Start => "flex-start",
634
1
            Self::End => "flex-end",
635
1
            Self::SpaceBetween => "space-between",
636
1
            Self::SpaceAround => "space-around",
637
        })
638
6
    }
639
}
640

            
641
#[cfg(feature = "parser")]
642
#[derive(Clone, PartialEq, Eq)]
643
pub enum AlignContentParseError<'a> {
644
    InvalidValue(&'a str),
645
}
646

            
647
#[cfg(feature = "parser")]
648
impl_debug_as_display!(AlignContentParseError<'a>);
649
#[cfg(feature = "parser")]
650
impl_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)]
657
pub enum AlignContentParseErrorOwned {
658
    InvalidValue(AzString),
659
}
660

            
661
#[cfg(feature = "parser")]
662
impl AlignContentParseError<'_> {
663
6
    #[must_use] pub fn to_contained(&self) -> AlignContentParseErrorOwned {
664
6
        match self {
665
6
            Self::InvalidValue(s) => AlignContentParseErrorOwned::InvalidValue((*s).to_string().into()),
666
        }
667
6
    }
668
}
669

            
670
#[cfg(feature = "parser")]
671
impl AlignContentParseErrorOwned {
672
7
    #[must_use] pub fn to_shared(&self) -> AlignContentParseError<'_> {
673
7
        match self {
674
7
            Self::InvalidValue(s) => AlignContentParseError::InvalidValue(s.as_str()),
675
        }
676
7
    }
677
}
678

            
679
#[cfg(feature = "parser")]
680
/// # Errors
681
///
682
/// Returns an error if `input` is not a valid CSS `align-content` value.
683
36
pub fn parse_layout_align_content(
684
36
    input: &str,
685
36
) -> Result<LayoutAlignContent, AlignContentParseError<'_>> {
686
36
    match input.trim() {
687
36
        "stretch" => Ok(LayoutAlignContent::Stretch),
688
34
        "center" => Ok(LayoutAlignContent::Center),
689
32
        "start" | "flex-start" => Ok(LayoutAlignContent::Start),
690
30
        "end" | "flex-end" => Ok(LayoutAlignContent::End),
691
26
        "space-between" => Ok(LayoutAlignContent::SpaceBetween),
692
24
        "space-around" => Ok(LayoutAlignContent::SpaceAround),
693
22
        _ => Err(AlignContentParseError::InvalidValue(input)),
694
    }
695
36
}
696

            
697
// --- align-self ---
698

            
699
/// Represents an `align-self` attribute, which allows the default alignment
700
/// (or the one specified by align-items) to be overridden for individual flex items.
701
/// Default: `Auto`
702
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
703
#[repr(C)]
704
#[derive(Default)]
705
pub enum LayoutAlignSelf {
706
    #[default]
707
    Auto,
708
    Stretch,
709
    Center,
710
    Start,
711
    End,
712
    Baseline,
713
}
714

            
715

            
716
impl PrintAsCssValue for LayoutAlignSelf {
717
6
    fn print_as_css_value(&self) -> String {
718
6
        String::from(match self {
719
1
            Self::Auto => "auto",
720
1
            Self::Stretch => "stretch",
721
1
            Self::Center => "center",
722
1
            Self::Start => "flex-start",
723
1
            Self::End => "flex-end",
724
1
            Self::Baseline => "baseline",
725
        })
726
6
    }
727
}
728

            
729
impl 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)]
747
pub enum AlignSelfParseError<'a> {
748
    InvalidValue(&'a str),
749
}
750

            
751
#[cfg(feature = "parser")]
752
impl_debug_as_display!(AlignSelfParseError<'a>);
753
#[cfg(feature = "parser")]
754
impl_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)]
761
pub enum AlignSelfParseErrorOwned {
762
    InvalidValue(AzString),
763
}
764

            
765
#[cfg(feature = "parser")]
766
impl AlignSelfParseError<'_> {
767
6
    #[must_use] pub fn to_contained(&self) -> AlignSelfParseErrorOwned {
768
6
        match self {
769
6
            Self::InvalidValue(s) => AlignSelfParseErrorOwned::InvalidValue((*s).to_string().into()),
770
        }
771
6
    }
772
}
773

            
774
#[cfg(feature = "parser")]
775
impl AlignSelfParseErrorOwned {
776
7
    #[must_use] pub fn to_shared(&self) -> AlignSelfParseError<'_> {
777
7
        match self {
778
7
            Self::InvalidValue(s) => AlignSelfParseError::InvalidValue(s.as_str()),
779
        }
780
7
    }
781
}
782

            
783
#[cfg(feature = "parser")]
784
/// # Errors
785
///
786
/// Returns an error if `input` is not a valid CSS `align-self` value.
787
54
pub fn parse_layout_align_self(
788
54
    input: &str,
789
54
) -> Result<LayoutAlignSelf, AlignSelfParseError<'_>> {
790
54
    match input.trim() {
791
54
        "auto" => Ok(LayoutAlignSelf::Auto),
792
51
        "stretch" => Ok(LayoutAlignSelf::Stretch),
793
38
        "center" => Ok(LayoutAlignSelf::Center),
794
37
        "start" | "flex-start" => Ok(LayoutAlignSelf::Start),
795
34
        "end" | "flex-end" => Ok(LayoutAlignSelf::End),
796
21
        "baseline" => Ok(LayoutAlignSelf::Baseline),
797
20
        _ => Err(AlignSelfParseError::InvalidValue(input)),
798
    }
799
54
}
800

            
801
// --- flex-basis ---
802
#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
803
/// Represents a `flex-basis` attribute
804
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
805
#[repr(C, u8)]
806
#[derive(Default)]
807
pub enum LayoutFlexBasis {
808
    /// auto
809
    #[default]
810
    Auto,
811
    /// Fixed size
812
    Exact(crate::props::basic::pixel::PixelValue),
813
}
814

            
815
impl 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

            
822
impl PrintAsCssValue for LayoutFlexBasis {
823
8
    fn print_as_css_value(&self) -> String {
824
8
        match self {
825
1
            Self::Auto => "auto".to_string(),
826
7
            Self::Exact(px) => px.print_as_css_value(),
827
        }
828
8
    }
829
}
830

            
831
impl 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)]
847
pub enum FlexBasisParseError<'a> {
848
    InvalidValue(&'a str),
849
}
850

            
851
#[cfg(feature = "parser")]
852
impl_debug_as_display!(FlexBasisParseError<'a>);
853
#[cfg(feature = "parser")]
854
impl_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)]
861
pub enum FlexBasisParseErrorOwned {
862
    InvalidValue(AzString),
863
}
864

            
865
#[cfg(feature = "parser")]
866
impl FlexBasisParseError<'_> {
867
6
    #[must_use] pub fn to_contained(&self) -> FlexBasisParseErrorOwned {
868
6
        match self {
869
6
            FlexBasisParseError::InvalidValue(s) => {
870
6
                FlexBasisParseErrorOwned::InvalidValue((*s).to_string().into())
871
            }
872
        }
873
6
    }
874
}
875

            
876
#[cfg(feature = "parser")]
877
impl FlexBasisParseErrorOwned {
878
7
    #[must_use] pub fn to_shared(&self) -> FlexBasisParseError<'_> {
879
7
        match self {
880
7
            Self::InvalidValue(s) => {
881
7
                FlexBasisParseError::InvalidValue(s.as_str())
882
            }
883
        }
884
7
    }
885
}
886

            
887
#[cfg(feature = "parser")]
888
/// # Errors
889
///
890
/// Returns an error if `input` is not a valid CSS `flex-basis` value.
891
75
pub fn parse_layout_flex_basis(
892
75
    input: &str,
893
75
) -> Result<LayoutFlexBasis, FlexBasisParseError<'_>> {
894
    use crate::props::basic::pixel::parse_pixel_value;
895

            
896
75
    match input.trim() {
897
75
        "auto" => Ok(LayoutFlexBasis::Auto),
898
59
        s => parse_pixel_value(s)
899
59
            .map(LayoutFlexBasis::Exact)
900
59
            .map_err(|_| FlexBasisParseError::InvalidValue(input)),
901
    }
902
75
}
903

            
904
#[cfg(all(test, feature = "parser"))]
905
mod tests {
906
    // Tests assert that parsed values equal the exact source literals.
907
    #![allow(clippy::float_cmp)]
908
    use super::*;
909
    use crate::props::basic::pixel::PixelValue;
910

            
911
    #[test]
912
1
    fn test_parse_layout_flex_grow() {
913
1
        assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
914
1
        assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
915
1
        assert_eq!(parse_layout_flex_grow("2.5").unwrap().inner.get(), 2.5);
916
1
        assert_eq!(parse_layout_flex_grow("  0.5  ").unwrap().inner.get(), 0.5);
917
1
        assert!(parse_layout_flex_grow("none").is_err());
918
1
        assert!(parse_layout_flex_grow("-1").is_err()); // Negative values are invalid
919
1
    }
920

            
921
    #[test]
922
1
    fn test_parse_layout_flex_shrink() {
923
1
        assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
924
1
        assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
925
1
        assert_eq!(parse_layout_flex_shrink("3.0").unwrap().inner.get(), 3.0);
926
1
        assert_eq!(parse_layout_flex_shrink(" 0.2 ").unwrap().inner.get(), 0.2);
927
1
        assert!(parse_layout_flex_shrink("auto").is_err());
928
1
        assert!(parse_layout_flex_shrink("-1").is_err()); // Negative values are invalid
929
1
    }
930

            
931
    #[test]
932
1
    fn test_parse_layout_flex_direction() {
933
1
        assert_eq!(
934
1
            parse_layout_flex_direction("row").unwrap(),
935
            LayoutFlexDirection::Row
936
        );
937
1
        assert_eq!(
938
1
            parse_layout_flex_direction("row-reverse").unwrap(),
939
            LayoutFlexDirection::RowReverse
940
        );
941
1
        assert_eq!(
942
1
            parse_layout_flex_direction("column").unwrap(),
943
            LayoutFlexDirection::Column
944
        );
945
1
        assert_eq!(
946
1
            parse_layout_flex_direction("column-reverse").unwrap(),
947
            LayoutFlexDirection::ColumnReverse
948
        );
949
1
        assert_eq!(
950
1
            parse_layout_flex_direction("  row  ").unwrap(),
951
            LayoutFlexDirection::Row
952
        );
953
1
        assert!(parse_layout_flex_direction("reversed-row").is_err());
954
1
    }
955

            
956
    #[test]
957
1
    fn test_parse_layout_flex_wrap() {
958
1
        assert_eq!(
959
1
            parse_layout_flex_wrap("nowrap").unwrap(),
960
            LayoutFlexWrap::NoWrap
961
        );
962
1
        assert_eq!(
963
1
            parse_layout_flex_wrap("wrap").unwrap(),
964
            LayoutFlexWrap::Wrap
965
        );
966
1
        assert_eq!(
967
1
            parse_layout_flex_wrap("wrap-reverse").unwrap(),
968
            LayoutFlexWrap::WrapReverse
969
        );
970
1
        assert_eq!(
971
1
            parse_layout_flex_wrap("  wrap  ").unwrap(),
972
            LayoutFlexWrap::Wrap
973
        );
974
1
        assert!(parse_layout_flex_wrap("wrap reverse").is_err());
975
1
    }
976

            
977
    #[test]
978
1
    fn test_parse_layout_justify_content() {
979
1
        assert_eq!(
980
1
            parse_layout_justify_content("flex-start").unwrap(),
981
            LayoutJustifyContent::FlexStart
982
        );
983
1
        assert_eq!(
984
1
            parse_layout_justify_content("flex-end").unwrap(),
985
            LayoutJustifyContent::FlexEnd
986
        );
987
1
        assert_eq!(
988
1
            parse_layout_justify_content("start").unwrap(),
989
            LayoutJustifyContent::Start
990
        );
991
1
        assert_eq!(
992
1
            parse_layout_justify_content("end").unwrap(),
993
            LayoutJustifyContent::End
994
        );
995
1
        assert_eq!(
996
1
            parse_layout_justify_content("center").unwrap(),
997
            LayoutJustifyContent::Center
998
        );
999
1
        assert_eq!(
1
            parse_layout_justify_content("space-between").unwrap(),
            LayoutJustifyContent::SpaceBetween
        );
1
        assert_eq!(
1
            parse_layout_justify_content("space-around").unwrap(),
            LayoutJustifyContent::SpaceAround
        );
1
        assert_eq!(
1
            parse_layout_justify_content("space-evenly").unwrap(),
            LayoutJustifyContent::SpaceEvenly
        );
1
        assert_eq!(
1
            parse_layout_justify_content("  center  ").unwrap(),
            LayoutJustifyContent::Center
        );
1
    }
    #[test]
1
    fn test_parse_layout_align_items() {
1
        assert_eq!(
1
            parse_layout_align_items("stretch").unwrap(),
            LayoutAlignItems::Stretch
        );
1
        assert_eq!(
1
            parse_layout_align_items("flex-start").unwrap(),
            LayoutAlignItems::Start
        );
1
        assert_eq!(
1
            parse_layout_align_items("flex-end").unwrap(),
            LayoutAlignItems::End
        );
1
        assert_eq!(
1
            parse_layout_align_items("start").unwrap(),
            LayoutAlignItems::Start
        );
1
        assert_eq!(
1
            parse_layout_align_items("end").unwrap(),
            LayoutAlignItems::End
        );
1
        assert_eq!(
1
            parse_layout_align_items("center").unwrap(),
            LayoutAlignItems::Center
        );
1
        assert_eq!(
1
            parse_layout_align_items("baseline").unwrap(),
            LayoutAlignItems::Baseline
        );
1
        assert!(parse_layout_align_items("invalid").is_err());
1
    }
    #[test]
1
    fn test_parse_layout_align_content() {
1
        assert_eq!(
1
            parse_layout_align_content("stretch").unwrap(),
            LayoutAlignContent::Stretch
        );
1
        assert_eq!(
1
            parse_layout_align_content("flex-start").unwrap(),
            LayoutAlignContent::Start
        );
1
        assert_eq!(
1
            parse_layout_align_content("flex-end").unwrap(),
            LayoutAlignContent::End
        );
1
        assert_eq!(
1
            parse_layout_align_content("center").unwrap(),
            LayoutAlignContent::Center
        );
1
        assert_eq!(
1
            parse_layout_align_content("space-between").unwrap(),
            LayoutAlignContent::SpaceBetween
        );
1
        assert_eq!(
1
            parse_layout_align_content("space-around").unwrap(),
            LayoutAlignContent::SpaceAround
        );
1
        assert!(parse_layout_align_content("space-evenly").is_err()); // Not valid for align-content
1
    }
    #[test]
1
    fn test_parse_layout_flex_basis() {
1
        assert_eq!(
1
            parse_layout_flex_basis("auto").unwrap(),
            LayoutFlexBasis::Auto
        );
1
        assert_eq!(
1
            parse_layout_flex_basis("200px").unwrap(),
1
            LayoutFlexBasis::Exact(PixelValue::px(200.0))
        );
1
        assert_eq!(
1
            parse_layout_flex_basis("50%").unwrap(),
1
            LayoutFlexBasis::Exact(PixelValue::percent(50.0))
        );
1
        assert_eq!(
1
            parse_layout_flex_basis("  10em  ").unwrap(),
1
            LayoutFlexBasis::Exact(PixelValue::em(10.0))
        );
1
        assert!(parse_layout_flex_basis("none").is_err());
        // Liberal parsing accepts unitless numbers (treated as px)
1
        assert_eq!(
1
            parse_layout_flex_basis("200").unwrap(),
1
            LayoutFlexBasis::Exact(PixelValue::px(200.0))
        );
1
        assert_eq!(
1
            parse_layout_flex_basis("0").unwrap(),
1
            LayoutFlexBasis::Exact(PixelValue::px(0.0))
        );
1
    }
}
#[cfg(test)]
#[allow(clippy::float_cmp)] // fixed-point (1/1000) values are exactly representable in f32
mod autotest_generated {
    use super::*;
    use crate::props::basic::{length::FloatValue, pixel::PixelValue};
    // ---------------------------------------------------------------------
    // LayoutFlexGrow::new / const_new  (constructor + numeric)
    // ---------------------------------------------------------------------
    /// `new()` goes through f32 (`isize -> f32 -> *1000 -> isize`) while
    /// `const_new()` uses pure integer math (`value * 1000`). For magnitudes
    /// where both encodings are exact they must agree bit-for-bit.
    #[test]
    fn flex_grow_new_agrees_with_const_new_for_exact_ints() {
        for v in [-10_000_isize, -1000, -7, -1, 0, 1, 7, 1000, 10_000] {
            assert_eq!(
                LayoutFlexGrow::new(v).inner.number(),
                LayoutFlexGrow::const_new(v).inner.number(),
                "new()/const_new() disagree for {v}"
            );
            assert_eq!(LayoutFlexGrow::new(v).inner.get(), v as f32);
        }
    }
    /// `new()` runs the value through `f32 as isize`, which saturates rather
    /// than wrapping or panicking: MIN/MAX must survive without UB or overflow.
    #[test]
    fn flex_grow_new_saturates_at_isize_extremes() {
        let max = LayoutFlexGrow::new(isize::MAX);
        let min = LayoutFlexGrow::new(isize::MIN);
        assert_eq!(max.inner.number(), isize::MAX, "MAX must saturate, not wrap");
        assert_eq!(min.inner.number(), isize::MIN, "MIN must saturate, not wrap");
        assert!(max.inner.get().is_finite());
        assert!(min.inner.get().is_finite());
        assert!(max.inner.get() > 0.0);
        assert!(min.inner.get() < 0.0);
    }
    /// `const_new()` stores `value * 1000` in an `isize`, so the largest safe
    /// input is `isize::MAX / 1000`. Anything above that overflows the
    /// multiplication (debug-panic / release-wrap) — this pins the documented
    /// safe boundary. See the report note on `const_new` overflow.
    #[test]
    fn flex_grow_const_new_at_safe_encoding_boundary() {
        let hi = isize::MAX / 1000;
        let lo = isize::MIN / 1000;
        assert_eq!(LayoutFlexGrow::const_new(hi).inner.number(), hi * 1000);
        assert_eq!(LayoutFlexGrow::const_new(lo).inner.number(), lo * 1000);
        assert!(LayoutFlexGrow::const_new(hi).inner.get().is_finite());
        assert!(LayoutFlexGrow::const_new(lo).inner.get().is_finite());
    }
    /// Zero / negative inputs are stored verbatim: the constructors perform no
    /// CSS validation (`flex-grow` may not be negative), only the parser does.
    #[test]
    fn flex_grow_const_new_zero_and_negative_are_not_clamped() {
        const ZERO: LayoutFlexGrow = LayoutFlexGrow::const_new(0);
        const NEG: LayoutFlexGrow = LayoutFlexGrow::const_new(-3);
        assert_eq!(ZERO.inner.get(), 0.0);
        assert_eq!(ZERO.inner.number(), 0);
        assert_eq!(NEG.inner.get(), -3.0);
        assert_eq!(LayoutFlexGrow::new(-3).inner.get(), -3.0);
    }
    #[test]
    fn flex_grow_and_shrink_defaults_match_css_initial_values() {
        assert_eq!(LayoutFlexGrow::default().inner.get(), 0.0);
        assert_eq!(LayoutFlexShrink::default().inner.get(), 1.0);
        // Ord is derived over the fixed-point isize, so it must track the float.
        assert!(LayoutFlexGrow::const_new(1) < LayoutFlexGrow::const_new(2));
        assert!(LayoutFlexGrow::const_new(-1) < LayoutFlexGrow::const_new(0));
    }
    // ---------------------------------------------------------------------
    // interpolate()  (numeric: zero / limits / NaN / inf / overflow)
    // ---------------------------------------------------------------------
    fn grow(v: f32) -> LayoutFlexGrow {
        LayoutFlexGrow {
            inner: FloatValue::new(v),
        }
    }
    fn shrink(v: f32) -> LayoutFlexShrink {
        LayoutFlexShrink {
            inner: FloatValue::new(v),
        }
    }
    #[test]
    fn flex_grow_interpolate_endpoints_midpoint_and_extrapolation() {
        let a = grow(0.0);
        let b = grow(10.0);
        assert_eq!(a.interpolate(&b, 0.0).inner.get(), 0.0);
        assert_eq!(a.interpolate(&b, 1.0).inner.get(), 10.0);
        assert_eq!(a.interpolate(&b, 0.5).inner.get(), 5.0);
        // t is not clamped: extrapolation past the endpoints is well-defined.
        assert_eq!(a.interpolate(&b, 2.0).inner.get(), 20.0);
        assert_eq!(a.interpolate(&b, -1.0).inner.get(), -10.0);
    }
    /// A NaN `t` produces NaN internally; the `f32 as isize` cast maps NaN to 0,
    /// so the result is a defined 0.0 rather than a NaN or a panic.
    #[test]
    fn flex_grow_interpolate_nan_t_collapses_to_zero() {
        let a = grow(2.0);
        let b = grow(8.0);
        let out = a.interpolate(&b, f32::NAN);
        assert!(out.inner.get().is_finite(), "NaN must not leak into the value");
        assert_eq!(out.inner.get(), 0.0);
        assert_eq!(out.inner.number(), 0);
    }
    /// Infinite `t` overflows the lerp; the saturating cast must keep the result
    /// finite and correctly signed instead of panicking.
    #[test]
    fn flex_grow_interpolate_infinite_t_saturates_finite() {
        let a = grow(0.0);
        let b = grow(10.0);
        let pos = a.interpolate(&b, f32::INFINITY);
        assert!(pos.inner.get().is_finite());
        assert!(pos.inner.get() > 0.0);
        assert_eq!(pos.inner.number(), isize::MAX);
        let neg = a.interpolate(&b, f32::NEG_INFINITY);
        assert!(neg.inner.get().is_finite());
        assert!(neg.inner.get() < 0.0);
        assert_eq!(neg.inner.number(), isize::MIN);
        // Degenerate case: equal endpoints => (b - a) * inf == NaN => 0.0.
        let same = grow(4.0).interpolate(&grow(4.0), f32::INFINITY);
        assert!(same.inner.get().is_finite());
        assert_eq!(same.inner.get(), 0.0);
    }
    /// Interpolating between the saturated extremes must never panic and must
    /// always yield a finite, decodable value.
    #[test]
    fn flex_grow_interpolate_extreme_endpoints_stay_finite() {
        let a = LayoutFlexGrow::new(isize::MAX);
        let b = LayoutFlexGrow::new(isize::MIN);
        for t in [
            0.0_f32,
            0.5,
            1.0,
            -1.0,
            1e30,
            -1e30,
            f32::MIN_POSITIVE,
            f32::NAN,
            f32::INFINITY,
            f32::NEG_INFINITY,
        ] {
            let out = a.interpolate(&b, t);
            assert!(
                out.inner.get().is_finite(),
                "interpolate(MAX, MIN, {t}) produced a non-finite value"
            );
        }
        // t = 0 must return `self` even at the saturation boundary.
        assert_eq!(a.interpolate(&b, 0.0).inner.get(), a.inner.get());
    }
    #[test]
    fn flex_shrink_interpolate_endpoints_nan_and_inf() {
        let a = shrink(1.0);
        let b = shrink(3.0);
        assert_eq!(a.interpolate(&b, 0.0).inner.get(), 1.0);
        assert_eq!(a.interpolate(&b, 1.0).inner.get(), 3.0);
        assert_eq!(a.interpolate(&b, 0.5).inner.get(), 2.0);
        assert_eq!(a.interpolate(&b, -2.0).inner.get(), -3.0);
        assert_eq!(a.interpolate(&b, f32::NAN).inner.get(), 0.0);
        assert!(a.interpolate(&b, f32::INFINITY).inner.get().is_finite());
        assert!(a.interpolate(&b, f32::NEG_INFINITY).inner.get().is_finite());
        let extreme = LayoutFlexShrink {
            inner: FloatValue::new(f32::MAX),
        };
        assert!(extreme.interpolate(&a, 0.5).inner.get().is_finite());
        assert!(extreme.interpolate(&a, f32::NAN).inner.get().is_finite());
    }
    // ---------------------------------------------------------------------
    // LayoutFlexDirection::get_axis / is_reverse  (getter + predicate)
    // ---------------------------------------------------------------------
    const ALL_DIRECTIONS: [LayoutFlexDirection; 4] = [
        LayoutFlexDirection::Row,
        LayoutFlexDirection::RowReverse,
        LayoutFlexDirection::Column,
        LayoutFlexDirection::ColumnReverse,
    ];
    #[test]
    fn flex_direction_get_axis_is_exhaustive_and_reverse_invariant() {
        assert_eq!(LayoutFlexDirection::Row.get_axis(), LayoutAxis::Horizontal);
        assert_eq!(
            LayoutFlexDirection::RowReverse.get_axis(),
            LayoutAxis::Horizontal
        );
        assert_eq!(LayoutFlexDirection::Column.get_axis(), LayoutAxis::Vertical);
        assert_eq!(
            LayoutFlexDirection::ColumnReverse.get_axis(),
            LayoutAxis::Vertical
        );
        // Invariant: reversing a direction never changes its axis.
        assert_eq!(
            LayoutFlexDirection::Row.get_axis(),
            LayoutFlexDirection::RowReverse.get_axis()
        );
        assert_eq!(
            LayoutFlexDirection::Column.get_axis(),
            LayoutFlexDirection::ColumnReverse.get_axis()
        );
        // Default (`row`) must be the horizontal, non-reversed axis.
        assert_eq!(LayoutFlexDirection::default().get_axis(), LayoutAxis::Horizontal);
        assert!(!LayoutFlexDirection::default().is_reverse());
    }
    #[test]
    fn flex_direction_is_reverse_exhaustive() {
        assert!(!LayoutFlexDirection::Row.is_reverse());
        assert!(LayoutFlexDirection::RowReverse.is_reverse());
        assert!(!LayoutFlexDirection::Column.is_reverse());
        assert!(LayoutFlexDirection::ColumnReverse.is_reverse());
        // Every variant answers deterministically, and exactly half are reverse.
        let reversed = ALL_DIRECTIONS.iter().filter(|d| d.is_reverse()).count();
        assert_eq!(reversed, 2);
        // get_axis()/is_reverse() are orthogonal: both axes have a reverse form.
        for axis in [LayoutAxis::Horizontal, LayoutAxis::Vertical] {
            assert_eq!(
                ALL_DIRECTIONS
                    .iter()
                    .filter(|d| d.get_axis() == axis && d.is_reverse())
                    .count(),
                1
            );
        }
    }
    // =====================================================================
    // Parser-gated tests
    // =====================================================================
    // --- flex-grow / flex-shrink (numeric parsers) -----------------------
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_parse_empty_and_whitespace_only_are_err() {
        for input in ["", " ", "   ", "\t", "\n", "\r\n", "\t \n "] {
            assert!(
                parse_layout_flex_grow(input).is_err(),
                "empty/whitespace input {input:?} must not parse"
            );
            assert!(parse_layout_flex_shrink(input).is_err());
        }
    }
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_parse_garbage_and_unicode_never_panics() {
        let deep_nesting = "(".repeat(10_000) + &")".repeat(10_000);
        let long_junk = "x".repeat(1_000_000);
        let long_digits = "9".repeat(100_000);
        let garbage = [
            "none",
            "auto",
            "null",
            "1/2",
            "0x10",
            "1,0",
            "--1",
            "1-",
            "+-1",
            "1e",
            "e1",
            "\u{1F600}",             // emoji
            "1\u{1F600}",            // digit + emoji
            "e\u{0301}",             // combining acute accent
            "\u{0661}\u{0662}",      // arabic-indic digits
            "1",                     // fullwidth digit
            "1\u{0}",                // embedded NUL
            "\u{200B}1",             // zero-width space
            deep_nesting.as_str(),
            long_junk.as_str(),
        ];
        for input in garbage {
            assert!(
                parse_layout_flex_grow(input).is_err(),
                "garbage input {:?} must be rejected",
                input.chars().take(8).collect::<String>()
            );
            assert!(parse_layout_flex_shrink(input).is_err());
        }
        // 100k digits overflows f32 to +inf; it must saturate, not hang or panic.
        let huge = parse_layout_flex_grow(&long_digits).expect("huge finite-overflow input");
        assert!(huge.inner.get().is_finite());
        assert!(huge.inner.get() >= 0.0);
    }
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_parse_boundary_numbers() {
        // Positive controls.
        assert_eq!(parse_layout_flex_grow("0").unwrap().inner.get(), 0.0);
        assert_eq!(parse_layout_flex_grow("1").unwrap().inner.get(), 1.0);
        assert_eq!(parse_layout_flex_grow("+2.5").unwrap().inner.get(), 2.5);
        assert_eq!(parse_layout_flex_grow(".5").unwrap().inner.get(), 0.5);
        // Signed zero is accepted and normalised to +0.
        let neg_zero = parse_layout_flex_grow("-0").unwrap();
        assert_eq!(neg_zero.inner.get(), 0.0);
        assert_eq!(neg_zero.inner.number(), 0);
        // Genuinely negative values are rejected.
        assert!(matches!(
            parse_layout_flex_grow("-1"),
            Err(FlexGrowParseError::NegativeValue("-1"))
        ));
        assert!(parse_layout_flex_grow("-0.01").is_err());
        assert!(parse_layout_flex_grow("-1e10").is_err());
        // -inf underflows the fixed point to isize::MIN => still caught as negative.
        assert!(matches!(
            parse_layout_flex_grow("-inf"),
            Err(FlexGrowParseError::NegativeValue(_))
        ));
        // Sub-quantum negatives (|v| < 0.001) truncate to 0 and are ACCEPTED —
        // the negative check runs after the fixed-point quantisation.
        let tiny_neg = parse_layout_flex_grow("-0.0001").unwrap();
        assert_eq!(tiny_neg.inner.get(), 0.0);
        assert_eq!(parse_layout_flex_grow("-1e-30").unwrap().inner.get(), 0.0);
        // Values beyond f32 range become +inf, then saturate to a finite maximum.
        for input in ["inf", "1e40", "9223372036854775807", "3.5e38"] {
            let parsed = parse_layout_flex_grow(input)
                .unwrap_or_else(|e| panic!("{input:?} unexpectedly rejected: {e}"));
            assert!(
                parsed.inner.get().is_finite(),
                "{input:?} decoded to a non-finite value"
            );
            assert!(parsed.inner.get() >= 0.0);
        }
        // Denormal-scale positives quantise to 0 rather than erroring.
        assert_eq!(parse_layout_flex_grow("1e-40").unwrap().inner.get(), 0.0);
        // "NaN" is a valid Rust float literal, so it reaches the fixed-point
        // cast, which maps NaN -> 0. It is accepted as flex-grow: 0.
        let nan = parse_layout_flex_grow("NaN").unwrap();
        assert!(nan.inner.get().is_finite());
        assert_eq!(nan.inner.get(), 0.0);
    }
    #[cfg(feature = "parser")]
    #[test]
    fn flex_shrink_parse_boundary_numbers() {
        assert_eq!(parse_layout_flex_shrink("0").unwrap().inner.get(), 0.0);
        assert_eq!(parse_layout_flex_shrink("1").unwrap().inner.get(), 1.0);
        assert_eq!(parse_layout_flex_shrink("-0").unwrap().inner.get(), 0.0);
        assert!(matches!(
            parse_layout_flex_shrink("-1"),
            Err(FlexShrinkParseError::NegativeValue("-1"))
        ));
        assert!(parse_layout_flex_shrink("-inf").is_err());
        let huge = parse_layout_flex_shrink("1e40").unwrap();
        assert!(huge.inner.get().is_finite() && huge.inner.get() > 0.0);
        assert_eq!(parse_layout_flex_shrink("NaN").unwrap().inner.get(), 0.0);
    }
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_parse_leading_trailing_junk() {
        // Surrounding whitespace is trimmed.
        assert_eq!(parse_layout_flex_grow("  0.5  ").unwrap().inner.get(), 0.5);
        assert_eq!(parse_layout_flex_grow("\t2\n").unwrap().inner.get(), 2.0);
        // Trailing junk / units / extra tokens are rejected.
        for input in ["1;", "1 2", "1px", "1%", "valid;garbage", "1 1 1"] {
            assert!(
                parse_layout_flex_grow(input).is_err(),
                "{input:?} must be rejected"
            );
        }
    }
    /// The error must carry the *original* (untrimmed) input, not the trimmed
    /// slice — callers rely on it to point back into the source CSS.
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_error_preserves_untrimmed_input() {
        match parse_layout_flex_grow("  bogus  ") {
            Err(FlexGrowParseError::ParseFloat(_, s)) => assert_eq!(s, "  bogus  "),
            other => panic!("expected ParseFloat error, got {other:?}"),
        }
        match parse_layout_flex_shrink(" -2 ") {
            Err(FlexShrinkParseError::NegativeValue(s)) => assert_eq!(s, " -2 "),
            other => panic!("expected NegativeValue error, got {other:?}"),
        }
    }
    /// Round-trip: print -> parse must reproduce the value for anything that is
    /// exactly representable in the 1/1000 fixed point.
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_shrink_print_parse_round_trip() {
        for v in [0.0_f32, 1.0, 2.5, 0.25, 0.125, 100.0, 12.5] {
            let g = grow(v);
            let printed = g.print_as_css_value();
            assert_eq!(
                parse_layout_flex_grow(&printed).unwrap().inner.number(),
                g.inner.number(),
                "flex-grow round-trip failed for {printed}"
            );
            let s = shrink(v);
            let printed = s.print_as_css_value();
            assert_eq!(
                parse_layout_flex_shrink(&printed).unwrap().inner.number(),
                s.inner.number(),
                "flex-shrink round-trip failed for {printed}"
            );
        }
    }
    // --- keyword parsers -------------------------------------------------
    /// Every enum variant must survive `print_as_css_value() -> parse()`.
    #[cfg(feature = "parser")]
    #[test]
    fn keyword_enums_print_parse_round_trip() {
        for d in ALL_DIRECTIONS {
            assert_eq!(parse_layout_flex_direction(&d.print_as_css_value()).unwrap(), d);
        }
        for w in [
            LayoutFlexWrap::Wrap,
            LayoutFlexWrap::NoWrap,
            LayoutFlexWrap::WrapReverse,
        ] {
            assert_eq!(parse_layout_flex_wrap(&w.print_as_css_value()).unwrap(), w);
        }
        for j in [
            LayoutJustifyContent::FlexStart,
            LayoutJustifyContent::FlexEnd,
            LayoutJustifyContent::Start,
            LayoutJustifyContent::End,
            LayoutJustifyContent::Center,
            LayoutJustifyContent::SpaceBetween,
            LayoutJustifyContent::SpaceAround,
            LayoutJustifyContent::SpaceEvenly,
        ] {
            assert_eq!(parse_layout_justify_content(&j.print_as_css_value()).unwrap(), j);
        }
        for a in [
            LayoutAlignItems::Stretch,
            LayoutAlignItems::Center,
            LayoutAlignItems::Start,
            LayoutAlignItems::End,
            LayoutAlignItems::Baseline,
        ] {
            assert_eq!(parse_layout_align_items(&a.print_as_css_value()).unwrap(), a);
        }
        for a in [
            LayoutAlignContent::Stretch,
            LayoutAlignContent::Center,
            LayoutAlignContent::Start,
            LayoutAlignContent::End,
            LayoutAlignContent::SpaceBetween,
            LayoutAlignContent::SpaceAround,
        ] {
            assert_eq!(parse_layout_align_content(&a.print_as_css_value()).unwrap(), a);
        }
        for a in [
            LayoutAlignSelf::Auto,
            LayoutAlignSelf::Stretch,
            LayoutAlignSelf::Center,
            LayoutAlignSelf::Start,
            LayoutAlignSelf::End,
            LayoutAlignSelf::Baseline,
        ] {
            assert_eq!(parse_layout_align_self(&a.print_as_css_value()).unwrap(), a);
        }
    }
    #[cfg(feature = "parser")]
    #[test]
    fn keyword_parsers_reject_empty_whitespace_and_garbage() {
        let deep_nesting = "[".repeat(10_000) + &"]".repeat(10_000);
        let long_junk = "row".repeat(300_000); // ~900k chars, no hang
        let bad = [
            "",
            " ",
            "\t\n",
            "0",
            "-1",
            "NaN",
            "inf",
            "9223372036854775807",
            "\u{1F600}",
            "row\u{200B}",  // zero-width space is NOT css whitespace
            "row\u{0}",     // embedded NUL
            "row row",
            "row;",
            ";row",
            "row/**/",
            deep_nesting.as_str(),
            long_junk.as_str(),
        ];
        for input in bad {
            assert!(parse_layout_flex_direction(input).is_err());
            assert!(parse_layout_flex_wrap(input).is_err());
            assert!(parse_layout_justify_content(input).is_err());
            assert!(parse_layout_align_items(input).is_err());
            assert!(parse_layout_align_content(input).is_err());
            assert!(parse_layout_align_self(input).is_err());
        }
    }
    /// CSS keywords are ASCII case-insensitive, but these parsers match
    /// case-sensitively. Pinned as current behaviour (see report).
    #[cfg(feature = "parser")]
    #[test]
    fn keyword_parsers_are_case_sensitive() {
        assert!(parse_layout_flex_direction("ROW").is_err());
        assert!(parse_layout_flex_direction("Row").is_err());
        assert!(parse_layout_flex_wrap("NoWrap").is_err());
        assert!(parse_layout_justify_content("Center").is_err());
        assert!(parse_layout_align_items("STRETCH").is_err());
        assert!(parse_layout_align_content("Stretch").is_err());
        assert!(parse_layout_align_self("AUTO").is_err());
        // lowercase positive controls still work
        assert_eq!(
            parse_layout_flex_direction("row").unwrap(),
            LayoutFlexDirection::Row
        );
        assert_eq!(parse_layout_align_self("auto").unwrap(), LayoutAlignSelf::Auto);
    }
    /// Keyword errors must echo the original, untrimmed input.
    #[cfg(feature = "parser")]
    #[test]
    fn keyword_errors_preserve_untrimmed_input() {
        assert_eq!(
            parse_layout_flex_direction("  bogus  ").unwrap_err(),
            FlexDirectionParseError::InvalidValue("  bogus  ")
        );
        assert_eq!(
            parse_layout_flex_wrap("\twrap!\n").unwrap_err(),
            FlexWrapParseError::InvalidValue("\twrap!\n")
        );
        assert_eq!(
            parse_layout_justify_content("").unwrap_err(),
            JustifyContentParseError::InvalidValue("")
        );
        assert_eq!(
            parse_layout_align_items(" nope ").unwrap_err(),
            AlignItemsParseError::InvalidValue(" nope ")
        );
        assert_eq!(
            parse_layout_align_content(" nope ").unwrap_err(),
            AlignContentParseError::InvalidValue(" nope ")
        );
        assert_eq!(
            parse_layout_align_self(" nope ").unwrap_err(),
            AlignSelfParseError::InvalidValue(" nope ")
        );
    }
    /// Aliases: `start`/`flex-start` and `end`/`flex-end` collapse to the same
    /// variant for align-*, while justify-content keeps them distinct.
    #[cfg(feature = "parser")]
    #[test]
    fn align_aliases_collapse_but_justify_keeps_them_distinct() {
        assert_eq!(
            parse_layout_align_items("start").unwrap(),
            parse_layout_align_items("flex-start").unwrap()
        );
        assert_eq!(
            parse_layout_align_content("end").unwrap(),
            parse_layout_align_content("flex-end").unwrap()
        );
        assert_eq!(
            parse_layout_align_self("start").unwrap(),
            parse_layout_align_self("flex-start").unwrap()
        );
        assert_ne!(
            parse_layout_justify_content("start").unwrap(),
            parse_layout_justify_content("flex-start").unwrap()
        );
        // space-evenly exists for justify-content but not for align-content.
        assert!(parse_layout_justify_content("space-evenly").is_ok());
        assert!(parse_layout_align_content("space-evenly").is_err());
        // align-self has `auto`; align-items does not.
        assert!(parse_layout_align_self("auto").is_ok());
        assert!(parse_layout_align_items("auto").is_err());
    }
    // --- flex-basis ------------------------------------------------------
    #[cfg(feature = "parser")]
    #[test]
    fn flex_basis_print_parse_round_trip() {
        for basis in [
            LayoutFlexBasis::Auto,
            LayoutFlexBasis::Exact(PixelValue::px(0.0)),
            LayoutFlexBasis::Exact(PixelValue::px(200.0)),
            LayoutFlexBasis::Exact(PixelValue::px(-5.0)),
            LayoutFlexBasis::Exact(PixelValue::percent(50.0)),
            LayoutFlexBasis::Exact(PixelValue::em(10.5)),
            LayoutFlexBasis::Exact(PixelValue::rem(1.25)),
            LayoutFlexBasis::Exact(PixelValue::pt(12.0)),
        ] {
            let printed = basis.print_as_css_value();
            assert_eq!(
                parse_layout_flex_basis(&printed).unwrap(),
                basis,
                "flex-basis round-trip failed for {printed}"
            );
        }
    }
    #[cfg(feature = "parser")]
    #[test]
    fn flex_basis_rejects_empty_units_only_and_garbage() {
        let deep_nesting = "(".repeat(10_000) + &")".repeat(10_000);
        let long_junk = "z".repeat(1_000_000);
        for input in [
            "",
            "   ",
            "\t\n",
            "px",          // unit with no value
            "%",
            "em",
            " px ",
            "none",
            "auto auto",
            "200px;",
            "200 px extra",
            "AUTO",        // case-sensitive
            "5PX",
            "\u{1F600}",
            "50px",       // fullwidth digits
            "200\u{200B}px",
            deep_nesting.as_str(),
            long_junk.as_str(),
        ] {
            assert!(
                parse_layout_flex_basis(input).is_err(),
                "flex-basis {:?} must be rejected",
                input.chars().take(10).collect::<String>()
            );
        }
        // The error echoes the original, untrimmed input.
        assert_eq!(
            parse_layout_flex_basis("  none  ").unwrap_err(),
            FlexBasisParseError::InvalidValue("  none  ")
        );
    }
    /// Adversarial numeric flex-basis inputs: NaN/inf reach the fixed-point cast
    /// through the unit suffix and must saturate to a finite, defined value.
    #[cfg(feature = "parser")]
    #[test]
    fn flex_basis_nan_and_inf_units_saturate() {
        // "NaN" is a valid float literal => NaNpx decodes to 0px, not an error.
        assert_eq!(
            parse_layout_flex_basis("NaNpx").unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(0.0))
        );
        // Overflowing magnitudes saturate rather than panicking.
        assert_eq!(
            parse_layout_flex_basis("infpx").unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
        );
        assert_eq!(
            parse_layout_flex_basis("1e40px").unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
        );
        assert_eq!(
            parse_layout_flex_basis(&"9".repeat(100_000)).unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(f32::INFINITY))
        );
        // Unitless numbers are accepted and treated as px (liberal parsing).
        assert_eq!(
            parse_layout_flex_basis("-0").unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(0.0))
        );
        // Negative lengths are accepted even though CSS forbids them (see report).
        assert_eq!(
            parse_layout_flex_basis("-5px").unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(-5.0))
        );
        // Whitespace *inside* the token is tolerated (see report).
        assert_eq!(
            parse_layout_flex_basis("5 px").unwrap(),
            LayoutFlexBasis::Exact(PixelValue::px(5.0))
        );
    }
    // ---------------------------------------------------------------------
    // Error to_contained() / to_shared()  (getters, borrow <-> owned)
    // ---------------------------------------------------------------------
    /// `to_contained()` then `to_shared()` must be the identity for every error
    /// variant, including empty / unicode / long payloads.
    #[cfg(feature = "parser")]
    #[test]
    fn flex_grow_shrink_error_owned_round_trip() {
        let invalid = "x".parse::<f32>().unwrap_err();
        let empty = "".parse::<f32>().unwrap_err();
        let long = "q".repeat(10_000);
        for payload in ["", "abc", "\u{1F600}\u{0301}", "  spaced  ", long.as_str()] {
            for err in [
                FlexGrowParseError::ParseFloat(invalid.clone(), payload),
                FlexGrowParseError::ParseFloat(empty.clone(), payload),
                FlexGrowParseError::NegativeValue(payload),
            ] {
                assert_eq!(err.to_contained().to_shared(), err);
            }
            for err in [
                FlexShrinkParseError::ParseFloat(invalid.clone(), payload),
                FlexShrinkParseError::NegativeValue(payload),
            ] {
                assert_eq!(err.to_contained().to_shared(), err);
            }
        }
    }
    #[cfg(feature = "parser")]
    #[test]
    fn keyword_error_owned_round_trip() {
        let long = "k".repeat(10_000);
        for payload in ["", " ", "\u{1F600}", "bogus", long.as_str()] {
            let d = FlexDirectionParseError::InvalidValue(payload);
            assert_eq!(d.to_contained().to_shared(), d);
            let w = FlexWrapParseError::InvalidValue(payload);
            assert_eq!(w.to_contained().to_shared(), w);
            let j = JustifyContentParseError::InvalidValue(payload);
            assert_eq!(j.to_contained().to_shared(), j);
            let ai = AlignItemsParseError::InvalidValue(payload);
            assert_eq!(ai.to_contained().to_shared(), ai);
            let ac = AlignContentParseError::InvalidValue(payload);
            assert_eq!(ac.to_contained().to_shared(), ac);
            let asf = AlignSelfParseError::InvalidValue(payload);
            assert_eq!(asf.to_contained().to_shared(), asf);
            let b = FlexBasisParseError::InvalidValue(payload);
            assert_eq!(b.to_contained().to_shared(), b);
        }
    }
    /// `to_shared()` must not panic on a directly-constructed owned error with a
    /// degenerate (empty) payload, and must hand back the exact same string.
    #[cfg(feature = "parser")]
    #[test]
    fn owned_errors_to_shared_on_degenerate_payloads() {
        let empty: AzString = String::new().into();
        assert_eq!(
            FlexDirectionParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            FlexDirectionParseError::InvalidValue("")
        );
        assert_eq!(
            FlexWrapParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            FlexWrapParseError::InvalidValue("")
        );
        assert_eq!(
            JustifyContentParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            JustifyContentParseError::InvalidValue("")
        );
        assert_eq!(
            AlignItemsParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            AlignItemsParseError::InvalidValue("")
        );
        assert_eq!(
            AlignContentParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            AlignContentParseError::InvalidValue("")
        );
        assert_eq!(
            AlignSelfParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            AlignSelfParseError::InvalidValue("")
        );
        assert_eq!(
            FlexBasisParseErrorOwned::InvalidValue(empty.clone()).to_shared(),
            FlexBasisParseError::InvalidValue("")
        );
        assert_eq!(
            FlexGrowParseErrorOwned::NegativeValue(empty.clone()).to_shared(),
            FlexGrowParseError::NegativeValue("")
        );
        assert_eq!(
            FlexShrinkParseErrorOwned::NegativeValue(empty).to_shared(),
            FlexShrinkParseError::NegativeValue("")
        );
    }
    /// Errors surfaced by the real parsers must convert to owned form and
    /// render a non-empty message that names the offending property.
    #[cfg(feature = "parser")]
    #[test]
    fn parser_errors_to_contained_and_display() {
        let e = parse_layout_flex_grow("bogus").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("flex-grow"));
        let e = parse_layout_flex_shrink("-1").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("flex-shrink"));
        let e = parse_layout_flex_direction("\u{1F600}").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("flex-direction"));
        let e = parse_layout_flex_wrap("").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("flex-wrap"));
        let e = parse_layout_justify_content("nope").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("justify-content"));
        let e = parse_layout_align_items("nope").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("align-items"));
        let e = parse_layout_align_content("nope").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("align-content"));
        let e = parse_layout_align_self("nope").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("align-self"));
        let e = parse_layout_flex_basis("none").unwrap_err();
        assert_eq!(e.to_contained().to_shared(), e);
        assert!(format!("{e}").contains("flex-basis"));
    }
}