Skip to main content

taffy/style/
alignment.rs

1//! Style types for controlling alignment.
2//!
3//! The public alignment types ([`AlignItems`], [`AlignContent`], and their aliases) are
4//! structs with two orthogonal fields: a *position* keyword
5//! ([`AlignItemsKeyword`] / [`AlignContentKeyword`]) and an *overflow-position*
6//! modifier ([`AlignmentSafety`]). The pre-existing CSS spellings — `Start`, `End`,
7//! `FlexStart`, `FlexEnd`, `Center`, `Stretch`, `SpaceBetween`, …, `SafeStart`,
8//! `SafeEnd`, `SafeFlexStart`, `SafeFlexEnd`, `SafeCenter` — are exposed as associated
9//! constants on the structs, so call sites read identically to the previous enum form.
10
11#[cfg(feature = "parse")]
12use crate::util::parse::{CssParseResult, FromCss, Parser, Token};
13
14use crate::style::Direction;
15
16/// The position-keyword half of [`AlignItems`] (and its aliases `AlignSelf`,
17/// `JustifyItems`, `JustifySelf`).
18///
19/// Compute paths match on this enum directly so every match is exhaustive and
20/// requires no `Safe*` siblings.
21#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[repr(u8)]
24pub enum AlignItemsKeyword {
25    /// The layout mode's default alignment: usually `Stretch`, but `Start` in some cases.
26    #[default]
27    Normal,
28    /// Items are packed toward the start of the axis.
29    Start,
30    /// Items are packed toward the end of the axis.
31    End,
32    /// Items are packed towards the flex-relative start of the axis.
33    ///
34    /// For flex containers with flex_direction RowReverse or ColumnReverse this is
35    /// equivalent to End. In all other cases it is equivalent to Start.
36    FlexStart,
37    /// Items are packed towards the flex-relative end of the axis.
38    ///
39    /// For flex containers with flex_direction RowReverse or ColumnReverse this is
40    /// equivalent to Start. In all other cases it is equivalent to End.
41    FlexEnd,
42    /// Items are packed toward the start of the axis as determined by the item's own
43    /// writing mode/direction (rather than the container's).
44    ///
45    /// Equivalent to Start when the item's `direction` matches the container's, and
46    /// to End when they differ (in the inline axis).
47    SelfStart,
48    /// Items are packed toward the end of the axis as determined by the item's own
49    /// writing mode/direction (rather than the container's).
50    ///
51    /// Equivalent to End when the item's `direction` matches the container's, and
52    /// to Start when they differ (in the inline axis).
53    SelfEnd,
54    /// Items are packed along the center of the cross axis.
55    Center,
56    /// Items are aligned such as their baselines align.
57    Baseline,
58    /// Stretch to fill the container.
59    Stretch,
60}
61
62/// The position-keyword half of [`AlignContent`] (and its alias `JustifyContent`).
63///
64/// Compute paths match on this enum directly so every match is exhaustive and
65/// requires no `Safe*` siblings.
66#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
67#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68#[repr(u8)]
69pub enum AlignContentKeyword {
70    /// The layout mode's default alignment: `Stretch` for flex and grid containers, `Start`
71    /// for block containers.
72    #[default]
73    Normal,
74    /// Items are packed toward the start of the axis.
75    Start,
76    /// Items are packed toward the end of the axis.
77    End,
78    /// Items are packed towards the flex-relative start of the axis.
79    FlexStart,
80    /// Items are packed towards the flex-relative end of the axis.
81    FlexEnd,
82    /// Items are centered around the middle of the axis.
83    Center,
84    /// Items are stretched to fill the container.
85    Stretch,
86    /// The first and last items are aligned flush with the edges of the container
87    /// (no gap). The gap between items is distributed evenly.
88    SpaceBetween,
89    /// The gap between the first and last items is exactly THE SAME as the gap
90    /// between items. The gaps are distributed evenly.
91    SpaceEvenly,
92    /// The gap between the first and last items is exactly HALF the gap between
93    /// items. The gaps are distributed evenly in proportion to these ratios.
94    SpaceAround,
95}
96
97impl AlignContentKeyword {
98    /// Returns the reversed keyword for RTL (right-to-left) contexts: `Start`↔`End`,
99    /// `FlexStart`↔`FlexEnd`. `Stretch` maps to `End` to preserve the layout
100    /// algorithms' historical handling. `Normal`, `Center` and the distribution keywords
101    /// (`SpaceBetween`, `SpaceEvenly`, `SpaceAround`) are unaffected because their
102    /// visual placement is direction-symmetric.
103    pub(crate) fn reversed(self) -> Self {
104        match self {
105            Self::Start => Self::End,
106            Self::End => Self::Start,
107            Self::FlexStart => Self::FlexEnd,
108            Self::FlexEnd => Self::FlexStart,
109            Self::Stretch => Self::End,
110            Self::Normal | Self::Center | Self::SpaceBetween | Self::SpaceEvenly | Self::SpaceAround => self,
111        }
112    }
113}
114
115/// The overflow-position modifier per [CSS Box Alignment §4.3][css-align-overflow].
116///
117/// `Safe` falls back to start-edge alignment when the alignment subject would
118/// overflow the alignment container, so the start of the content stays visible.
119/// `Unsafe` keeps the requested alignment even when that causes overflow at the
120/// start edge. `Default` (no overflow-position keyword specified) behaves as
121/// `Unsafe`, except for absolutely positioned boxes whose alignment is instead
122/// adjusted to minimize overflow per [CSS Box Alignment §4.4.1.2][css-align-abspos].
123///
124/// CSS only defines `safe` / `unsafe` against the position values `start`, `end`,
125/// `flex-start`, `flex-end`, `center`. The struct shape does not enforce that
126/// constraint at the type level — the parser rejects invalid combinations, and
127/// the compute pass treats `Safe` paired with a non-position keyword (`Stretch`,
128/// `Baseline`, `Space*`) the same as `Unsafe`.
129///
130/// [css-align-overflow]: https://www.w3.org/TR/css-align-3/#overflow-values
131/// [css-align-abspos]: https://www.w3.org/TR/css-align-3/#auto-safety-position
132#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
133#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
134#[repr(u8)]
135pub enum AlignmentSafety {
136    /// No overflow-position keyword specified. Behaves as `Unsafe`, except for absolutely
137    /// positioned boxes, whose alignment is adjusted to minimize overflow of their
138    /// containing block.
139    #[default]
140    Default,
141    /// Keeps the requested alignment even when the subject overflows the alignment
142    /// container at the start edge.
143    Unsafe,
144    /// Falls back to the start edge when the subject would overflow, to avoid data
145    /// loss.
146    Safe,
147}
148
149/// Used to control how child nodes are aligned.
150/// For Flexbox it controls alignment in the cross axis.
151/// For Grid it controls alignment in the block axis.
152///
153/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
154#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
155pub struct AlignItems {
156    /// Position keyword.
157    pub keyword: AlignItemsKeyword,
158    /// Overflow-position modifier (`safe` / `unsafe`).
159    pub safety: AlignmentSafety,
160}
161
162impl AlignItems {
163    /// The layout mode's default alignment. See [`AlignItemsKeyword::Normal`].
164    pub const NORMAL: Self = Self { keyword: AlignItemsKeyword::Normal, safety: AlignmentSafety::Default };
165    /// Items are packed toward the start of the axis.
166    pub const START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Default };
167    /// Items are packed toward the end of the axis.
168    pub const END: Self = Self { keyword: AlignItemsKeyword::End, safety: AlignmentSafety::Default };
169    /// Items are packed towards the flex-relative start of the axis.
170    pub const FLEX_START: Self = Self { keyword: AlignItemsKeyword::FlexStart, safety: AlignmentSafety::Default };
171    /// Items are packed towards the flex-relative end of the axis.
172    pub const FLEX_END: Self = Self { keyword: AlignItemsKeyword::FlexEnd, safety: AlignmentSafety::Default };
173    /// Items are packed toward the start of the axis as determined by the item's own direction.
174    pub const SELF_START: Self = Self { keyword: AlignItemsKeyword::SelfStart, safety: AlignmentSafety::Default };
175    /// Items are packed toward the end of the axis as determined by the item's own direction.
176    pub const SELF_END: Self = Self { keyword: AlignItemsKeyword::SelfEnd, safety: AlignmentSafety::Default };
177    /// Items are packed along the center of the cross axis.
178    pub const CENTER: Self = Self { keyword: AlignItemsKeyword::Center, safety: AlignmentSafety::Default };
179    /// Items are aligned such as their baselines align.
180    pub const BASELINE: Self = Self { keyword: AlignItemsKeyword::Baseline, safety: AlignmentSafety::Default };
181    /// Stretch to fill the container.
182    pub const STRETCH: Self = Self { keyword: AlignItemsKeyword::Stretch, safety: AlignmentSafety::Default };
183    /// Like [`AlignItems::START`], but falls back to [`AlignItems::START`] when the
184    /// alignment subject overflows the alignment container, to avoid data loss.
185    pub const SAFE_START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Safe };
186    /// Like [`AlignItems::END`], but falls back to [`AlignItems::START`] when the
187    /// alignment subject overflows the alignment container, to avoid data loss.
188    pub const SAFE_END: Self = Self { keyword: AlignItemsKeyword::End, safety: AlignmentSafety::Safe };
189    /// Like [`AlignItems::FLEX_START`], but falls back to [`AlignItems::START`] when the
190    /// alignment subject overflows the alignment container, to avoid data loss.
191    pub const SAFE_FLEX_START: Self = Self { keyword: AlignItemsKeyword::FlexStart, safety: AlignmentSafety::Safe };
192    /// Like [`AlignItems::FLEX_END`], but falls back to [`AlignItems::START`] when the
193    /// alignment subject overflows the alignment container, to avoid data loss.
194    pub const SAFE_FLEX_END: Self = Self { keyword: AlignItemsKeyword::FlexEnd, safety: AlignmentSafety::Safe };
195    /// Like [`AlignItems::CENTER`], but falls back to [`AlignItems::START`] when the
196    /// alignment subject overflows the alignment container, to avoid data loss.
197    pub const SAFE_CENTER: Self = Self { keyword: AlignItemsKeyword::Center, safety: AlignmentSafety::Safe };
198    /// Like [`AlignItems::SELF_START`], but falls back to [`AlignItems::START`] when the
199    /// alignment subject overflows the alignment container, to avoid data loss.
200    pub const SAFE_SELF_START: Self = Self { keyword: AlignItemsKeyword::SelfStart, safety: AlignmentSafety::Safe };
201    /// Like [`AlignItems::SELF_END`], but falls back to [`AlignItems::START`] when the
202    /// alignment subject overflows the alignment container, to avoid data loss.
203    pub const SAFE_SELF_END: Self = Self { keyword: AlignItemsKeyword::SelfEnd, safety: AlignmentSafety::Safe };
204
205    /// Returns `true` iff this carries the `safe` overflow-position modifier.
206    #[inline]
207    pub const fn is_safe(self) -> bool {
208        matches!(self.safety, AlignmentSafety::Safe)
209    }
210
211    /// Returns this alignment with the given overflow-position modifier.
212    #[inline]
213    pub const fn with_safety(self, safety: AlignmentSafety) -> Self {
214        Self { keyword: self.keyword, safety }
215    }
216
217    /// Returns the underlying position keyword, discarding the safety modifier.
218    #[inline]
219    pub const fn keyword(self) -> AlignItemsKeyword {
220        self.keyword
221    }
222
223    /// Returns true if the keyword is `Stretch` or `Normal` (ignoring any overflow-position
224    /// modifier).
225    #[inline(always)]
226    pub const fn is_stretch_or_normal(self) -> bool {
227        matches!(self.keyword, AlignItemsKeyword::Stretch | AlignItemsKeyword::Normal)
228    }
229
230    /// Resolve the writing-mode-relative `SelfStart`/`SelfEnd` keywords to `Start`/`End`
231    /// based on the item's own `direction` per CSS Box Alignment §5.2
232    /// <https://www.w3.org/TR/css-align-3/#self-alignment>. All other keywords are
233    /// returned unchanged.
234    ///
235    /// The `Start`/`End` keywords used by the compute paths are relative to the
236    /// *container's* writing mode/direction, so in the inline axis `SelfStart` resolves
237    /// to `Start` when the item's direction matches the container's and to `End` when it
238    /// differs. Taffy only supports the `horizontal-tb` writing mode, so in the block
239    /// axis `SelfStart`/`SelfEnd` always resolve to `Start`/`End` respectively.
240    #[inline]
241    pub fn resolve_self_relative(
242        self,
243        item_direction: Direction,
244        container_direction: Direction,
245        axis_is_inline: bool,
246    ) -> Self {
247        let flip = axis_is_inline && item_direction != container_direction;
248        let keyword = match self.keyword {
249            AlignItemsKeyword::SelfStart => {
250                if flip {
251                    AlignItemsKeyword::End
252                } else {
253                    AlignItemsKeyword::Start
254                }
255            }
256            AlignItemsKeyword::SelfEnd => {
257                if flip {
258                    AlignItemsKeyword::Start
259                } else {
260                    AlignItemsKeyword::End
261                }
262            }
263            other => other,
264        };
265        Self { keyword, safety: self.safety }
266    }
267}
268
269#[cfg(feature = "parse")]
270impl FromCss for AlignItems {
271    fn from_css<'i>(input: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
272        let first = input.expect_ident()?.clone();
273        cssparser::match_ignore_ascii_case! { &*first,
274            "safe" => {
275                let pos = input.expect_ident()?.clone();
276                cssparser::match_ignore_ascii_case! { &*pos,
277                    "start" => Ok(Self::SAFE_START),
278                    "end" => Ok(Self::SAFE_END),
279                    "flex-start" => Ok(Self::SAFE_FLEX_START),
280                    "flex-end" => Ok(Self::SAFE_FLEX_END),
281                    "self-start" => Ok(Self::SAFE_SELF_START),
282                    "self-end" => Ok(Self::SAFE_SELF_END),
283                    "center" => Ok(Self::SAFE_CENTER),
284                    _ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
285                }
286            },
287            "unsafe" => {
288                let pos = input.expect_ident()?.clone();
289                let keyword = cssparser::match_ignore_ascii_case! { &*pos,
290                    "start" => AlignItemsKeyword::Start,
291                    "end" => AlignItemsKeyword::End,
292                    "flex-start" => AlignItemsKeyword::FlexStart,
293                    "flex-end" => AlignItemsKeyword::FlexEnd,
294                    "self-start" => AlignItemsKeyword::SelfStart,
295                    "self-end" => AlignItemsKeyword::SelfEnd,
296                    "center" => AlignItemsKeyword::Center,
297                    _ => return Err(input.new_unexpected_token_error(Token::Ident(pos))),
298                };
299                Ok(Self { keyword, safety: AlignmentSafety::Unsafe })
300            },
301            "normal" => Ok(Self::NORMAL),
302            "start" => Ok(Self::START),
303            "end" => Ok(Self::END),
304            "flex-start" => Ok(Self::FLEX_START),
305            "flex-end" => Ok(Self::FLEX_END),
306            "self-start" => Ok(Self::SELF_START),
307            "self-end" => Ok(Self::SELF_END),
308            "center" => Ok(Self::CENTER),
309            "baseline" => Ok(Self::BASELINE),
310            "stretch" => Ok(Self::STRETCH),
311            _ => Err(input.new_unexpected_token_error(Token::Ident(first))),
312        }
313    }
314}
315
316#[cfg(feature = "parse")]
317crate::util::parse::from_str_from_css!(AlignItems);
318
319/// Used to control how child nodes are aligned.
320/// Does not apply to Flexbox, and will be ignored if specified on a flex container.
321/// For Grid it controls alignment in the inline axis.
322///
323/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items)
324pub type JustifyItems = AlignItems;
325/// Controls alignment of an individual node.
326///
327/// Overrides the parent Node's `AlignItems` property.
328/// For Flexbox it controls alignment in the cross axis.
329/// For Grid it controls alignment in the block axis.
330///
331/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-self)
332pub type AlignSelf = AlignItems;
333/// Controls alignment of an individual node.
334///
335/// Overrides the parent Node's `JustifyItems` property.
336/// Does not apply to Flexbox, and will be ignored if specified on a flex child.
337/// For Grid it controls alignment in the inline axis.
338///
339/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self)
340pub type JustifySelf = AlignItems;
341
342/// Sets the distribution of space between and around content items.
343/// For Flexbox it controls alignment in the cross axis.
344/// For Grid it controls alignment in the block axis.
345///
346/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content)
347#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
348pub struct AlignContent {
349    /// Position keyword.
350    pub keyword: AlignContentKeyword,
351    /// Overflow-position modifier (`safe` / `unsafe`).
352    pub safety: AlignmentSafety,
353}
354
355impl AlignContent {
356    /// The layout mode's default alignment. See [`AlignContentKeyword::Normal`].
357    pub const NORMAL: Self = Self { keyword: AlignContentKeyword::Normal, safety: AlignmentSafety::Default };
358    /// Items are packed toward the start of the axis.
359    pub const START: Self = Self { keyword: AlignContentKeyword::Start, safety: AlignmentSafety::Default };
360    /// Items are packed toward the end of the axis.
361    pub const END: Self = Self { keyword: AlignContentKeyword::End, safety: AlignmentSafety::Default };
362    /// Items are packed towards the flex-relative start of the axis.
363    pub const FLEX_START: Self = Self { keyword: AlignContentKeyword::FlexStart, safety: AlignmentSafety::Default };
364    /// Items are packed towards the flex-relative end of the axis.
365    pub const FLEX_END: Self = Self { keyword: AlignContentKeyword::FlexEnd, safety: AlignmentSafety::Default };
366    /// Items are centered around the middle of the axis.
367    pub const CENTER: Self = Self { keyword: AlignContentKeyword::Center, safety: AlignmentSafety::Default };
368    /// Items are stretched to fill the container.
369    pub const STRETCH: Self = Self { keyword: AlignContentKeyword::Stretch, safety: AlignmentSafety::Default };
370    /// The first and last items are aligned flush with the edges of the container.
371    pub const SPACE_BETWEEN: Self =
372        Self { keyword: AlignContentKeyword::SpaceBetween, safety: AlignmentSafety::Default };
373    /// The gap between the first and last items equals the gap between items.
374    pub const SPACE_EVENLY: Self = Self { keyword: AlignContentKeyword::SpaceEvenly, safety: AlignmentSafety::Default };
375    /// The gap between the first and last items is half the gap between items.
376    pub const SPACE_AROUND: Self = Self { keyword: AlignContentKeyword::SpaceAround, safety: AlignmentSafety::Default };
377    /// Like [`AlignContent::START`], but falls back to [`AlignContent::START`] when the
378    /// content overflows the alignment container, to avoid data loss.
379    pub const SAFE_START: Self = Self { keyword: AlignContentKeyword::Start, safety: AlignmentSafety::Safe };
380    /// Like [`AlignContent::END`], but falls back to [`AlignContent::START`] when the
381    /// content overflows the alignment container, to avoid data loss.
382    pub const SAFE_END: Self = Self { keyword: AlignContentKeyword::End, safety: AlignmentSafety::Safe };
383    /// Like [`AlignContent::FLEX_START`], but falls back to [`AlignContent::START`] when
384    /// the content overflows the alignment container, to avoid data loss.
385    pub const SAFE_FLEX_START: Self = Self { keyword: AlignContentKeyword::FlexStart, safety: AlignmentSafety::Safe };
386    /// Like [`AlignContent::FLEX_END`], but falls back to [`AlignContent::START`] when the
387    /// content overflows the alignment container, to avoid data loss.
388    pub const SAFE_FLEX_END: Self = Self { keyword: AlignContentKeyword::FlexEnd, safety: AlignmentSafety::Safe };
389    /// Like [`AlignContent::CENTER`], but falls back to [`AlignContent::START`] when the
390    /// content overflows the alignment container, to avoid data loss.
391    pub const SAFE_CENTER: Self = Self { keyword: AlignContentKeyword::Center, safety: AlignmentSafety::Safe };
392
393    /// Returns `true` iff this carries the `safe` overflow-position modifier.
394    #[inline]
395    pub const fn is_safe(self) -> bool {
396        matches!(self.safety, AlignmentSafety::Safe)
397    }
398
399    /// Returns this alignment with the given overflow-position modifier.
400    #[inline]
401    pub const fn with_safety(self, safety: AlignmentSafety) -> Self {
402        Self { keyword: self.keyword, safety }
403    }
404
405    /// Returns the underlying position keyword, discarding the safety modifier.
406    #[inline]
407    pub const fn keyword(self) -> AlignContentKeyword {
408        self.keyword
409    }
410}
411
412#[cfg(feature = "parse")]
413impl FromCss for AlignContent {
414    fn from_css<'i>(input: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
415        let first = input.expect_ident()?.clone();
416        cssparser::match_ignore_ascii_case! { &*first,
417            "safe" => {
418                let pos = input.expect_ident()?.clone();
419                cssparser::match_ignore_ascii_case! { &*pos,
420                    "start" => Ok(Self::SAFE_START),
421                    "end" => Ok(Self::SAFE_END),
422                    "flex-start" => Ok(Self::SAFE_FLEX_START),
423                    "flex-end" => Ok(Self::SAFE_FLEX_END),
424                    "center" => Ok(Self::SAFE_CENTER),
425                    _ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
426                }
427            },
428            "unsafe" => {
429                let pos = input.expect_ident()?.clone();
430                let keyword = cssparser::match_ignore_ascii_case! { &*pos,
431                    "start" => AlignContentKeyword::Start,
432                    "end" => AlignContentKeyword::End,
433                    "flex-start" => AlignContentKeyword::FlexStart,
434                    "flex-end" => AlignContentKeyword::FlexEnd,
435                    "center" => AlignContentKeyword::Center,
436                    _ => return Err(input.new_unexpected_token_error(Token::Ident(pos))),
437                };
438                Ok(Self { keyword, safety: AlignmentSafety::Unsafe })
439            },
440            "normal" => Ok(Self::NORMAL),
441            "start" => Ok(Self::START),
442            "end" => Ok(Self::END),
443            "flex-start" => Ok(Self::FLEX_START),
444            "flex-end" => Ok(Self::FLEX_END),
445            "center" => Ok(Self::CENTER),
446            "stretch" => Ok(Self::STRETCH),
447            "space-between" => Ok(Self::SPACE_BETWEEN),
448            "space-evenly" => Ok(Self::SPACE_EVENLY),
449            "space-around" => Ok(Self::SPACE_AROUND),
450            _ => Err(input.new_unexpected_token_error(Token::Ident(first))),
451        }
452    }
453}
454
455#[cfg(feature = "parse")]
456crate::util::parse::from_str_from_css!(AlignContent);
457
458/// Sets the distribution of space between and around content items.
459/// For Flexbox it controls alignment in the main axis.
460/// For Grid it controls alignment in the inline axis.
461///
462/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content)
463pub type JustifyContent = AlignContent;
464
465// ---------------------------------------------------------------------------
466// Serde — custom impls preserve the pre-struct wire format (single tag string
467// per public spelling) so consumers reading data serialized before the refactor
468// continue to deserialize correctly.
469// ---------------------------------------------------------------------------
470
471/// Canonical tag-string set accepted by [`AlignItems`] serde deserialization, used in
472/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
473#[cfg(feature = "serde")]
474const ALIGN_ITEMS_NAMES: &[&str] = &[
475    "Normal",
476    "Start",
477    "End",
478    "FlexStart",
479    "FlexEnd",
480    "SelfStart",
481    "SelfEnd",
482    "Center",
483    "Baseline",
484    "Stretch",
485    "SafeStart",
486    "SafeEnd",
487    "SafeFlexStart",
488    "SafeFlexEnd",
489    "SafeSelfStart",
490    "SafeSelfEnd",
491    "SafeCenter",
492    "UnsafeStart",
493    "UnsafeEnd",
494    "UnsafeFlexStart",
495    "UnsafeFlexEnd",
496    "UnsafeSelfStart",
497    "UnsafeSelfEnd",
498    "UnsafeCenter",
499];
500
501#[cfg(feature = "serde")]
502impl serde::Serialize for AlignItems {
503    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
504        let name = match (self.keyword, self.safety) {
505            (AlignItemsKeyword::Start, AlignmentSafety::Default) => "Start",
506            (AlignItemsKeyword::End, AlignmentSafety::Default) => "End",
507            (AlignItemsKeyword::FlexStart, AlignmentSafety::Default) => "FlexStart",
508            (AlignItemsKeyword::FlexEnd, AlignmentSafety::Default) => "FlexEnd",
509            (AlignItemsKeyword::SelfStart, AlignmentSafety::Default) => "SelfStart",
510            (AlignItemsKeyword::SelfEnd, AlignmentSafety::Default) => "SelfEnd",
511            (AlignItemsKeyword::Center, AlignmentSafety::Default) => "Center",
512            (AlignItemsKeyword::Start, AlignmentSafety::Unsafe) => "UnsafeStart",
513            (AlignItemsKeyword::End, AlignmentSafety::Unsafe) => "UnsafeEnd",
514            (AlignItemsKeyword::FlexStart, AlignmentSafety::Unsafe) => "UnsafeFlexStart",
515            (AlignItemsKeyword::FlexEnd, AlignmentSafety::Unsafe) => "UnsafeFlexEnd",
516            (AlignItemsKeyword::SelfStart, AlignmentSafety::Unsafe) => "UnsafeSelfStart",
517            (AlignItemsKeyword::SelfEnd, AlignmentSafety::Unsafe) => "UnsafeSelfEnd",
518            (AlignItemsKeyword::Center, AlignmentSafety::Unsafe) => "UnsafeCenter",
519            (AlignItemsKeyword::Normal, _) => "Normal",
520            (AlignItemsKeyword::Baseline, _) => "Baseline",
521            (AlignItemsKeyword::Stretch, _) => "Stretch",
522            (AlignItemsKeyword::Start, AlignmentSafety::Safe) => "SafeStart",
523            (AlignItemsKeyword::End, AlignmentSafety::Safe) => "SafeEnd",
524            (AlignItemsKeyword::FlexStart, AlignmentSafety::Safe) => "SafeFlexStart",
525            (AlignItemsKeyword::FlexEnd, AlignmentSafety::Safe) => "SafeFlexEnd",
526            (AlignItemsKeyword::SelfStart, AlignmentSafety::Safe) => "SafeSelfStart",
527            (AlignItemsKeyword::SelfEnd, AlignmentSafety::Safe) => "SafeSelfEnd",
528            (AlignItemsKeyword::Center, AlignmentSafety::Safe) => "SafeCenter",
529        };
530        serializer.serialize_str(name)
531    }
532}
533
534#[cfg(feature = "serde")]
535impl<'de> serde::Deserialize<'de> for AlignItems {
536    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
537        struct AlignItemsVisitor;
538        impl<'de> serde::de::Visitor<'de> for AlignItemsVisitor {
539            type Value = AlignItems;
540            fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
541                fmt.write_str("an AlignItems variant tag string")
542            }
543            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
544                Ok(match v {
545                    "Normal" => AlignItems::NORMAL,
546                    "Start" => AlignItems::START,
547                    "End" => AlignItems::END,
548                    "FlexStart" => AlignItems::FLEX_START,
549                    "FlexEnd" => AlignItems::FLEX_END,
550                    "SelfStart" => AlignItems::SELF_START,
551                    "SelfEnd" => AlignItems::SELF_END,
552                    "Center" => AlignItems::CENTER,
553                    "Baseline" => AlignItems::BASELINE,
554                    "Stretch" => AlignItems::STRETCH,
555                    "SafeStart" => AlignItems::SAFE_START,
556                    "SafeEnd" => AlignItems::SAFE_END,
557                    "SafeFlexStart" => AlignItems::SAFE_FLEX_START,
558                    "SafeFlexEnd" => AlignItems::SAFE_FLEX_END,
559                    "SafeSelfStart" => AlignItems::SAFE_SELF_START,
560                    "SafeSelfEnd" => AlignItems::SAFE_SELF_END,
561                    "SafeCenter" => AlignItems::SAFE_CENTER,
562                    "UnsafeStart" => AlignItems::START.with_safety(AlignmentSafety::Unsafe),
563                    "UnsafeEnd" => AlignItems::END.with_safety(AlignmentSafety::Unsafe),
564                    "UnsafeFlexStart" => AlignItems::FLEX_START.with_safety(AlignmentSafety::Unsafe),
565                    "UnsafeFlexEnd" => AlignItems::FLEX_END.with_safety(AlignmentSafety::Unsafe),
566                    "UnsafeSelfStart" => AlignItems::SELF_START.with_safety(AlignmentSafety::Unsafe),
567                    "UnsafeSelfEnd" => AlignItems::SELF_END.with_safety(AlignmentSafety::Unsafe),
568                    "UnsafeCenter" => AlignItems::CENTER.with_safety(AlignmentSafety::Unsafe),
569                    other => return Err(E::unknown_variant(other, ALIGN_ITEMS_NAMES)),
570                })
571            }
572        }
573        deserializer.deserialize_str(AlignItemsVisitor)
574    }
575}
576
577/// Canonical tag-string set accepted by [`AlignContent`] serde deserialization, used in
578/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
579#[cfg(feature = "serde")]
580const ALIGN_CONTENT_NAMES: &[&str] = &[
581    "Normal",
582    "Start",
583    "End",
584    "FlexStart",
585    "FlexEnd",
586    "Center",
587    "Stretch",
588    "SpaceBetween",
589    "SpaceEvenly",
590    "SpaceAround",
591    "SafeStart",
592    "SafeEnd",
593    "SafeFlexStart",
594    "SafeFlexEnd",
595    "SafeCenter",
596    "UnsafeStart",
597    "UnsafeEnd",
598    "UnsafeFlexStart",
599    "UnsafeFlexEnd",
600    "UnsafeCenter",
601];
602
603#[cfg(feature = "serde")]
604impl serde::Serialize for AlignContent {
605    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
606        let name = match (self.keyword, self.safety) {
607            (AlignContentKeyword::Start, AlignmentSafety::Default) => "Start",
608            (AlignContentKeyword::End, AlignmentSafety::Default) => "End",
609            (AlignContentKeyword::FlexStart, AlignmentSafety::Default) => "FlexStart",
610            (AlignContentKeyword::FlexEnd, AlignmentSafety::Default) => "FlexEnd",
611            (AlignContentKeyword::Center, AlignmentSafety::Default) => "Center",
612            (AlignContentKeyword::Start, AlignmentSafety::Unsafe) => "UnsafeStart",
613            (AlignContentKeyword::End, AlignmentSafety::Unsafe) => "UnsafeEnd",
614            (AlignContentKeyword::FlexStart, AlignmentSafety::Unsafe) => "UnsafeFlexStart",
615            (AlignContentKeyword::FlexEnd, AlignmentSafety::Unsafe) => "UnsafeFlexEnd",
616            (AlignContentKeyword::Center, AlignmentSafety::Unsafe) => "UnsafeCenter",
617            (AlignContentKeyword::Normal, _) => "Normal",
618            (AlignContentKeyword::Stretch, _) => "Stretch",
619            (AlignContentKeyword::SpaceBetween, _) => "SpaceBetween",
620            (AlignContentKeyword::SpaceEvenly, _) => "SpaceEvenly",
621            (AlignContentKeyword::SpaceAround, _) => "SpaceAround",
622            (AlignContentKeyword::Start, AlignmentSafety::Safe) => "SafeStart",
623            (AlignContentKeyword::End, AlignmentSafety::Safe) => "SafeEnd",
624            (AlignContentKeyword::FlexStart, AlignmentSafety::Safe) => "SafeFlexStart",
625            (AlignContentKeyword::FlexEnd, AlignmentSafety::Safe) => "SafeFlexEnd",
626            (AlignContentKeyword::Center, AlignmentSafety::Safe) => "SafeCenter",
627        };
628        serializer.serialize_str(name)
629    }
630}
631
632#[cfg(feature = "serde")]
633impl<'de> serde::Deserialize<'de> for AlignContent {
634    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
635        struct AlignContentVisitor;
636        impl<'de> serde::de::Visitor<'de> for AlignContentVisitor {
637            type Value = AlignContent;
638            fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
639                fmt.write_str("an AlignContent variant tag string")
640            }
641            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
642                Ok(match v {
643                    "Normal" => AlignContent::NORMAL,
644                    "Start" => AlignContent::START,
645                    "End" => AlignContent::END,
646                    "FlexStart" => AlignContent::FLEX_START,
647                    "FlexEnd" => AlignContent::FLEX_END,
648                    "Center" => AlignContent::CENTER,
649                    "Stretch" => AlignContent::STRETCH,
650                    "SpaceBetween" => AlignContent::SPACE_BETWEEN,
651                    "SpaceEvenly" => AlignContent::SPACE_EVENLY,
652                    "SpaceAround" => AlignContent::SPACE_AROUND,
653                    "SafeStart" => AlignContent::SAFE_START,
654                    "SafeEnd" => AlignContent::SAFE_END,
655                    "SafeFlexStart" => AlignContent::SAFE_FLEX_START,
656                    "SafeFlexEnd" => AlignContent::SAFE_FLEX_END,
657                    "SafeCenter" => AlignContent::SAFE_CENTER,
658                    "UnsafeStart" => AlignContent::START.with_safety(AlignmentSafety::Unsafe),
659                    "UnsafeEnd" => AlignContent::END.with_safety(AlignmentSafety::Unsafe),
660                    "UnsafeFlexStart" => AlignContent::FLEX_START.with_safety(AlignmentSafety::Unsafe),
661                    "UnsafeFlexEnd" => AlignContent::FLEX_END.with_safety(AlignmentSafety::Unsafe),
662                    "UnsafeCenter" => AlignContent::CENTER.with_safety(AlignmentSafety::Unsafe),
663                    other => return Err(E::unknown_variant(other, ALIGN_CONTENT_NAMES)),
664                })
665            }
666        }
667        deserializer.deserialize_str(AlignContentVisitor)
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674    use core::mem::size_of;
675
676    // Size budget — struct = 1B keyword + 1B safety, no niche packing.
677    // Pre-refactor each was a single-byte enum; spec §V13 caps regression at +2B.
678    #[test]
679    fn align_types_within_size_budget() {
680        assert!(size_of::<AlignItems>() <= 2, "AlignItems grew to {}", size_of::<AlignItems>());
681        assert!(size_of::<AlignContent>() <= 2, "AlignContent grew to {}", size_of::<AlignContent>());
682        assert!(size_of::<Option<AlignItems>>() <= 3);
683    }
684
685    #[test]
686    fn align_items_is_safe() {
687        assert!(AlignItems::SAFE_START.is_safe());
688        assert!(AlignItems::SAFE_END.is_safe());
689        assert!(AlignItems::SAFE_FLEX_START.is_safe());
690        assert!(AlignItems::SAFE_FLEX_END.is_safe());
691        assert!(AlignItems::SAFE_CENTER.is_safe());
692        assert!(!AlignItems::START.is_safe());
693        assert!(!AlignItems::END.is_safe());
694        assert!(!AlignItems::FLEX_START.is_safe());
695        assert!(!AlignItems::FLEX_END.is_safe());
696        assert!(!AlignItems::CENTER.is_safe());
697        assert!(!AlignItems::BASELINE.is_safe());
698        assert!(!AlignItems::STRETCH.is_safe());
699        assert!(AlignItems::SAFE_SELF_START.is_safe());
700        assert!(AlignItems::SAFE_SELF_END.is_safe());
701        assert!(!AlignItems::SELF_START.is_safe());
702        assert!(!AlignItems::SELF_END.is_safe());
703        assert!(!AlignItems::NORMAL.is_safe());
704    }
705
706    #[test]
707    fn align_items_keyword_strips_safe() {
708        assert_eq!(AlignItems::SAFE_START.keyword(), AlignItemsKeyword::Start);
709        assert_eq!(AlignItems::SAFE_END.keyword(), AlignItemsKeyword::End);
710        assert_eq!(AlignItems::SAFE_FLEX_START.keyword(), AlignItemsKeyword::FlexStart);
711        assert_eq!(AlignItems::SAFE_FLEX_END.keyword(), AlignItemsKeyword::FlexEnd);
712        assert_eq!(AlignItems::SAFE_CENTER.keyword(), AlignItemsKeyword::Center);
713        assert_eq!(AlignItems::SAFE_SELF_START.keyword(), AlignItemsKeyword::SelfStart);
714        assert_eq!(AlignItems::SAFE_SELF_END.keyword(), AlignItemsKeyword::SelfEnd);
715    }
716
717    #[test]
718    fn align_items_keyword_passthrough() {
719        assert_eq!(AlignItems::START.keyword(), AlignItemsKeyword::Start);
720        assert_eq!(AlignItems::STRETCH.keyword(), AlignItemsKeyword::Stretch);
721        assert_eq!(AlignItems::BASELINE.keyword(), AlignItemsKeyword::Baseline);
722        assert_eq!(AlignItems::FLEX_START.keyword(), AlignItemsKeyword::FlexStart);
723        assert_eq!(AlignItems::NORMAL.keyword(), AlignItemsKeyword::Normal);
724    }
725
726    #[test]
727    fn align_items_default_is_normal() {
728        assert_eq!(AlignItems::default(), AlignItems::NORMAL);
729        assert_eq!(AlignItemsKeyword::default(), AlignItemsKeyword::Normal);
730        assert_eq!(AlignmentSafety::default(), AlignmentSafety::Default);
731    }
732
733    #[test]
734    fn resolve_self_relative_inline_axis() {
735        use Direction::{Ltr, Rtl};
736        // Same direction: self-start == start, self-end == end
737        assert_eq!(AlignItems::SELF_START.resolve_self_relative(Ltr, Ltr, true), AlignItems::START);
738        assert_eq!(AlignItems::SELF_END.resolve_self_relative(Ltr, Ltr, true), AlignItems::END);
739        assert_eq!(AlignItems::SELF_START.resolve_self_relative(Rtl, Rtl, true), AlignItems::START);
740        assert_eq!(AlignItems::SELF_END.resolve_self_relative(Rtl, Rtl, true), AlignItems::END);
741        // Opposite direction: self-start == end, self-end == start
742        assert_eq!(AlignItems::SELF_START.resolve_self_relative(Ltr, Rtl, true), AlignItems::END);
743        assert_eq!(AlignItems::SELF_END.resolve_self_relative(Ltr, Rtl, true), AlignItems::START);
744        assert_eq!(AlignItems::SELF_START.resolve_self_relative(Rtl, Ltr, true), AlignItems::END);
745        assert_eq!(AlignItems::SELF_END.resolve_self_relative(Rtl, Ltr, true), AlignItems::START);
746        // Safety modifier is preserved
747        assert_eq!(AlignItems::SAFE_SELF_START.resolve_self_relative(Ltr, Rtl, true), AlignItems::SAFE_END);
748        // Other keywords are unchanged
749        assert_eq!(AlignItems::START.resolve_self_relative(Ltr, Rtl, true), AlignItems::START);
750        assert_eq!(AlignItems::FLEX_END.resolve_self_relative(Ltr, Rtl, true), AlignItems::FLEX_END);
751        assert_eq!(AlignItems::NORMAL.resolve_self_relative(Ltr, Rtl, true), AlignItems::NORMAL);
752    }
753
754    #[test]
755    fn resolve_self_relative_block_axis() {
756        use Direction::{Ltr, Rtl};
757        // In the block axis (horizontal-tb only) direction never flips self-start/self-end
758        assert_eq!(AlignItems::SELF_START.resolve_self_relative(Ltr, Rtl, false), AlignItems::START);
759        assert_eq!(AlignItems::SELF_END.resolve_self_relative(Rtl, Ltr, false), AlignItems::END);
760    }
761
762    #[test]
763    fn align_content_is_safe() {
764        assert!(AlignContent::SAFE_START.is_safe());
765        assert!(AlignContent::SAFE_CENTER.is_safe());
766        assert!(!AlignContent::SPACE_BETWEEN.is_safe());
767        assert!(!AlignContent::STRETCH.is_safe());
768        assert!(!AlignContent::NORMAL.is_safe());
769    }
770
771    #[test]
772    fn align_content_keyword_strips_safe() {
773        assert_eq!(AlignContent::SAFE_START.keyword(), AlignContentKeyword::Start);
774        assert_eq!(AlignContent::SAFE_FLEX_END.keyword(), AlignContentKeyword::FlexEnd);
775        assert_eq!(AlignContent::SAFE_CENTER.keyword(), AlignContentKeyword::Center);
776        assert_eq!(AlignContent::SPACE_BETWEEN.keyword(), AlignContentKeyword::SpaceBetween);
777        assert_eq!(AlignContent::NORMAL.keyword(), AlignContentKeyword::Normal);
778        assert_eq!(JustifyContent::NORMAL, AlignContent::NORMAL);
779    }
780
781    #[test]
782    fn align_content_keyword_reversed_swaps_start_end() {
783        assert_eq!(AlignContentKeyword::Start.reversed(), AlignContentKeyword::End);
784        assert_eq!(AlignContentKeyword::End.reversed(), AlignContentKeyword::Start);
785        assert_eq!(AlignContentKeyword::FlexStart.reversed(), AlignContentKeyword::FlexEnd);
786        assert_eq!(AlignContentKeyword::FlexEnd.reversed(), AlignContentKeyword::FlexStart);
787        // Stretch reverses to End — preserves pre-refactor behaviour.
788        assert_eq!(AlignContentKeyword::Stretch.reversed(), AlignContentKeyword::End);
789        assert_eq!(AlignContentKeyword::Normal.reversed(), AlignContentKeyword::Normal);
790        assert_eq!(AlignContentKeyword::Center.reversed(), AlignContentKeyword::Center);
791        assert_eq!(AlignContentKeyword::SpaceBetween.reversed(), AlignContentKeyword::SpaceBetween);
792        assert_eq!(AlignContentKeyword::SpaceEvenly.reversed(), AlignContentKeyword::SpaceEvenly);
793        assert_eq!(AlignContentKeyword::SpaceAround.reversed(), AlignContentKeyword::SpaceAround);
794    }
795
796    #[cfg(feature = "parse")]
797    #[test]
798    fn parse_align_items_plain() {
799        assert_eq!("start".parse::<AlignItems>().unwrap(), AlignItems::START);
800        assert_eq!("end".parse::<AlignItems>().unwrap(), AlignItems::END);
801        assert_eq!("flex-start".parse::<AlignItems>().unwrap(), AlignItems::FLEX_START);
802        assert_eq!("flex-end".parse::<AlignItems>().unwrap(), AlignItems::FLEX_END);
803        assert_eq!("center".parse::<AlignItems>().unwrap(), AlignItems::CENTER);
804        assert_eq!("self-start".parse::<AlignItems>().unwrap(), AlignItems::SELF_START);
805        assert_eq!("self-end".parse::<AlignItems>().unwrap(), AlignItems::SELF_END);
806        assert_eq!("baseline".parse::<AlignItems>().unwrap(), AlignItems::BASELINE);
807        assert_eq!("stretch".parse::<AlignItems>().unwrap(), AlignItems::STRETCH);
808    }
809
810    #[test]
811    fn align_content_default_is_normal() {
812        assert_eq!(AlignContent::default(), AlignContent::NORMAL);
813        assert_eq!(JustifyContent::default(), JustifyContent::NORMAL);
814    }
815
816    #[cfg(feature = "parse")]
817    #[test]
818    fn parse_align_items_normal() {
819        assert_eq!("normal".parse::<AlignItems>().unwrap(), AlignItems::NORMAL);
820        assert_eq!("NORMAL".parse::<AlignItems>().unwrap(), AlignItems::NORMAL);
821        assert_eq!(AlignItems::NORMAL.safety, AlignmentSafety::Default);
822        // `auto` is not an `AlignItems` keyword: it is represented by `None` on the `*-self` styles
823        assert!("auto".parse::<AlignSelf>().is_err());
824    }
825
826    #[cfg(feature = "parse")]
827    #[test]
828    fn parse_align_items_safe() {
829        assert_eq!("safe start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_START);
830        assert_eq!("safe end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_END);
831        assert_eq!("safe flex-start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_START);
832        assert_eq!("safe flex-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_END);
833        assert_eq!("safe self-start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_SELF_START);
834        assert_eq!("safe self-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_SELF_END);
835        assert_eq!("safe center".parse::<AlignItems>().unwrap(), AlignItems::SAFE_CENTER);
836    }
837
838    #[cfg(feature = "parse")]
839    #[test]
840    fn parse_align_items_safe_case_insensitive() {
841        assert_eq!("SAFE Start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_START);
842        assert_eq!("Safe FLEX-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_END);
843    }
844
845    #[cfg(feature = "parse")]
846    #[test]
847    fn parse_align_items_unsafe() {
848        let unsafe_ = |keyword| AlignItems { keyword, safety: AlignmentSafety::Unsafe };
849        assert_eq!("unsafe start".parse::<AlignItems>().unwrap(), unsafe_(AlignItemsKeyword::Start));
850        assert_eq!("unsafe end".parse::<AlignItems>().unwrap(), unsafe_(AlignItemsKeyword::End));
851        assert_eq!("unsafe self-start".parse::<AlignItems>().unwrap(), unsafe_(AlignItemsKeyword::SelfStart));
852        assert_eq!("unsafe self-end".parse::<AlignItems>().unwrap(), unsafe_(AlignItemsKeyword::SelfEnd));
853        assert_eq!("unsafe center".parse::<AlignItems>().unwrap(), unsafe_(AlignItemsKeyword::Center));
854        // The bare keyword carries no overflow-position modifier
855        assert_eq!(AlignItems::START.safety, AlignmentSafety::Default);
856    }
857
858    #[cfg(feature = "parse")]
859    #[test]
860    fn parse_align_items_rejects_invalid_safe_combos() {
861        assert!("safe stretch".parse::<AlignItems>().is_err());
862        assert!("safe baseline".parse::<AlignItems>().is_err());
863        assert!("safe space-between".parse::<AlignItems>().is_err());
864        assert!("safe".parse::<AlignItems>().is_err());
865        assert!("safe garbage".parse::<AlignItems>().is_err());
866        assert!("unsafe stretch".parse::<AlignItems>().is_err());
867        assert!("unsafe baseline".parse::<AlignItems>().is_err());
868        assert!("safe normal".parse::<AlignItems>().is_err());
869        assert!("safe auto".parse::<AlignItems>().is_err());
870        assert!("unsafe normal".parse::<AlignItems>().is_err());
871        assert!("unsafe auto".parse::<AlignItems>().is_err());
872    }
873
874    #[cfg(feature = "parse")]
875    #[test]
876    fn parse_align_content_plain() {
877        assert_eq!("normal".parse::<AlignContent>().unwrap(), AlignContent::NORMAL);
878        assert_eq!("Normal".parse::<JustifyContent>().unwrap(), JustifyContent::NORMAL);
879        assert_eq!(AlignContent::NORMAL.safety, AlignmentSafety::Default);
880        assert_eq!("start".parse::<AlignContent>().unwrap(), AlignContent::START);
881        assert_eq!("space-between".parse::<AlignContent>().unwrap(), AlignContent::SPACE_BETWEEN);
882        assert_eq!("space-evenly".parse::<AlignContent>().unwrap(), AlignContent::SPACE_EVENLY);
883        assert_eq!("space-around".parse::<AlignContent>().unwrap(), AlignContent::SPACE_AROUND);
884        assert_eq!("stretch".parse::<AlignContent>().unwrap(), AlignContent::STRETCH);
885    }
886
887    #[cfg(feature = "parse")]
888    #[test]
889    fn parse_align_content_safe() {
890        assert_eq!("safe start".parse::<AlignContent>().unwrap(), AlignContent::SAFE_START);
891        assert_eq!("safe end".parse::<AlignContent>().unwrap(), AlignContent::SAFE_END);
892        assert_eq!("safe flex-start".parse::<AlignContent>().unwrap(), AlignContent::SAFE_FLEX_START);
893        assert_eq!("safe flex-end".parse::<AlignContent>().unwrap(), AlignContent::SAFE_FLEX_END);
894        assert_eq!("safe center".parse::<AlignContent>().unwrap(), AlignContent::SAFE_CENTER);
895    }
896
897    #[cfg(feature = "parse")]
898    #[test]
899    fn parse_align_content_unsafe() {
900        let unsafe_ = |keyword| AlignContent { keyword, safety: AlignmentSafety::Unsafe };
901        assert_eq!("unsafe start".parse::<AlignContent>().unwrap(), unsafe_(AlignContentKeyword::Start));
902        assert_eq!("unsafe flex-end".parse::<AlignContent>().unwrap(), unsafe_(AlignContentKeyword::FlexEnd));
903    }
904
905    #[cfg(feature = "parse")]
906    #[test]
907    fn parse_align_content_rejects_invalid_safe_combos() {
908        assert!("safe stretch".parse::<AlignContent>().is_err());
909        assert!("safe space-between".parse::<AlignContent>().is_err());
910        assert!("safe space-evenly".parse::<AlignContent>().is_err());
911        assert!("safe space-around".parse::<AlignContent>().is_err());
912        assert!("safe".parse::<AlignContent>().is_err());
913        assert!("unsafe stretch".parse::<AlignContent>().is_err());
914        assert!("unsafe space-between".parse::<AlignContent>().is_err());
915        assert!("safe normal".parse::<AlignContent>().is_err());
916        assert!("unsafe normal".parse::<AlignContent>().is_err());
917        assert!("auto".parse::<AlignContent>().is_err());
918    }
919
920    #[cfg(feature = "serde")]
921    #[test]
922    fn serde_align_items_round_trip() {
923        let cases = [
924            (AlignItems::NORMAL, "\"Normal\""),
925            (AlignItems::START, "\"Start\""),
926            (AlignItems::END, "\"End\""),
927            (AlignItems::FLEX_START, "\"FlexStart\""),
928            (AlignItems::FLEX_END, "\"FlexEnd\""),
929            (AlignItems::CENTER, "\"Center\""),
930            (AlignItems::BASELINE, "\"Baseline\""),
931            (AlignItems::STRETCH, "\"Stretch\""),
932            (AlignItems::SAFE_START, "\"SafeStart\""),
933            (AlignItems::SAFE_END, "\"SafeEnd\""),
934            (AlignItems::SAFE_FLEX_START, "\"SafeFlexStart\""),
935            (AlignItems::SAFE_FLEX_END, "\"SafeFlexEnd\""),
936            (AlignItems::SAFE_CENTER, "\"SafeCenter\""),
937            (AlignItems::SELF_START, "\"SelfStart\""),
938            (AlignItems::SELF_END, "\"SelfEnd\""),
939            (AlignItems::SAFE_SELF_START, "\"SafeSelfStart\""),
940            (AlignItems::SAFE_SELF_END, "\"SafeSelfEnd\""),
941        ];
942        for (value, expected) in cases {
943            let serialized = serde_json::to_string(&value).unwrap();
944            assert_eq!(serialized, expected, "serialize {:?}", value);
945            let deserialized: AlignItems = serde_json::from_str(expected).unwrap();
946            assert_eq!(deserialized, value, "round-trip {:?}", value);
947        }
948        assert!(serde_json::from_str::<AlignItems>("\"NotAVariant\"").is_err());
949    }
950
951    #[cfg(feature = "serde")]
952    #[test]
953    fn serde_align_content_round_trip() {
954        let cases = [
955            (AlignContent::NORMAL, "\"Normal\""),
956            (AlignContent::START, "\"Start\""),
957            (AlignContent::END, "\"End\""),
958            (AlignContent::FLEX_START, "\"FlexStart\""),
959            (AlignContent::FLEX_END, "\"FlexEnd\""),
960            (AlignContent::CENTER, "\"Center\""),
961            (AlignContent::STRETCH, "\"Stretch\""),
962            (AlignContent::SPACE_BETWEEN, "\"SpaceBetween\""),
963            (AlignContent::SPACE_EVENLY, "\"SpaceEvenly\""),
964            (AlignContent::SPACE_AROUND, "\"SpaceAround\""),
965            (AlignContent::SAFE_START, "\"SafeStart\""),
966            (AlignContent::SAFE_END, "\"SafeEnd\""),
967            (AlignContent::SAFE_FLEX_START, "\"SafeFlexStart\""),
968            (AlignContent::SAFE_FLEX_END, "\"SafeFlexEnd\""),
969            (AlignContent::SAFE_CENTER, "\"SafeCenter\""),
970        ];
971        for (value, expected) in cases {
972            let serialized = serde_json::to_string(&value).unwrap();
973            assert_eq!(serialized, expected, "serialize {:?}", value);
974            let deserialized: AlignContent = serde_json::from_str(expected).unwrap();
975            assert_eq!(deserialized, value, "round-trip {:?}", value);
976        }
977        assert!(serde_json::from_str::<AlignContent>("\"NotAVariant\"").is_err());
978    }
979}