727 lines
34 KiB
Rust
727 lines
34 KiB
Rust
//! Style types for controlling alignment.
|
|
//!
|
|
//! The public alignment types ([`AlignItems`], [`AlignContent`], and their aliases) are
|
|
//! structs with two orthogonal fields: a *position* keyword
|
|
//! ([`AlignItemsKeyword`] / [`AlignContentKeyword`]) and an *overflow-position*
|
|
//! modifier ([`AlignmentSafety`]). The pre-existing CSS spellings — `Start`, `End`,
|
|
//! `FlexStart`, `FlexEnd`, `Center`, `Stretch`, `SpaceBetween`, …, `SafeStart`,
|
|
//! `SafeEnd`, `SafeFlexStart`, `SafeFlexEnd`, `SafeCenter` — are exposed as associated
|
|
//! constants on the structs, so call sites read identically to the previous enum form.
|
|
|
|
#[cfg(feature = "parse")]
|
|
use crate::util::parse::{CssParseResult, FromCss, Parser, Token};
|
|
|
|
/// The position-keyword half of [`AlignItems`] (and its aliases `AlignSelf`,
|
|
/// `JustifyItems`, `JustifySelf`).
|
|
///
|
|
/// Compute paths match on this enum directly so every match is exhaustive and
|
|
/// requires no `Safe*` siblings.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[repr(u8)]
|
|
pub enum AlignItemsKeyword {
|
|
/// Items are packed toward the start of the axis.
|
|
Start,
|
|
/// Items are packed toward the end of the axis.
|
|
End,
|
|
/// Items are packed towards the flex-relative start of the axis.
|
|
///
|
|
/// For flex containers with flex_direction RowReverse or ColumnReverse this is
|
|
/// equivalent to End. In all other cases it is equivalent to Start.
|
|
FlexStart,
|
|
/// Items are packed towards the flex-relative end of the axis.
|
|
///
|
|
/// For flex containers with flex_direction RowReverse or ColumnReverse this is
|
|
/// equivalent to Start. In all other cases it is equivalent to End.
|
|
FlexEnd,
|
|
/// Items are packed along the center of the cross axis.
|
|
Center,
|
|
/// Items are aligned such as their baselines align.
|
|
Baseline,
|
|
/// Stretch to fill the container.
|
|
Stretch,
|
|
}
|
|
|
|
/// The position-keyword half of [`AlignContent`] (and its alias `JustifyContent`).
|
|
///
|
|
/// Compute paths match on this enum directly so every match is exhaustive and
|
|
/// requires no `Safe*` siblings.
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[repr(u8)]
|
|
pub enum AlignContentKeyword {
|
|
/// Items are packed toward the start of the axis.
|
|
Start,
|
|
/// Items are packed toward the end of the axis.
|
|
End,
|
|
/// Items are packed towards the flex-relative start of the axis.
|
|
FlexStart,
|
|
/// Items are packed towards the flex-relative end of the axis.
|
|
FlexEnd,
|
|
/// Items are centered around the middle of the axis.
|
|
Center,
|
|
/// Items are stretched to fill the container.
|
|
Stretch,
|
|
/// The first and last items are aligned flush with the edges of the container
|
|
/// (no gap). The gap between items is distributed evenly.
|
|
SpaceBetween,
|
|
/// The gap between the first and last items is exactly THE SAME as the gap
|
|
/// between items. The gaps are distributed evenly.
|
|
SpaceEvenly,
|
|
/// The gap between the first and last items is exactly HALF the gap between
|
|
/// items. The gaps are distributed evenly in proportion to these ratios.
|
|
SpaceAround,
|
|
}
|
|
|
|
impl AlignContentKeyword {
|
|
/// Returns the reversed keyword for RTL (right-to-left) contexts: `Start`↔`End`,
|
|
/// `FlexStart`↔`FlexEnd`. `Stretch` maps to `End` to preserve the layout
|
|
/// algorithms' historical handling. Center and the distribution keywords
|
|
/// (`SpaceBetween`, `SpaceEvenly`, `SpaceAround`) are unaffected because their
|
|
/// visual placement is direction-symmetric.
|
|
pub(crate) fn reversed(self) -> Self {
|
|
match self {
|
|
Self::Start => Self::End,
|
|
Self::End => Self::Start,
|
|
Self::FlexStart => Self::FlexEnd,
|
|
Self::FlexEnd => Self::FlexStart,
|
|
Self::Stretch => Self::End,
|
|
Self::Center | Self::SpaceBetween | Self::SpaceEvenly | Self::SpaceAround => self,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The overflow-position modifier per [CSS Box Alignment §4.3][css-align-overflow].
|
|
///
|
|
/// `Safe` falls back to start-edge alignment when the alignment subject would
|
|
/// overflow the alignment container, so the start of the content stays visible.
|
|
/// `Unsafe` (the default) keeps the requested alignment even when that causes
|
|
/// overflow at the start edge.
|
|
///
|
|
/// CSS only defines `safe` / `unsafe` against the position values `start`, `end`,
|
|
/// `flex-start`, `flex-end`, `center`. The struct shape does not enforce that
|
|
/// constraint at the type level — the parser rejects invalid combinations, and
|
|
/// the compute pass treats `Safe` paired with a non-position keyword (`Stretch`,
|
|
/// `Baseline`, `Space*`) the same as `Unsafe`.
|
|
///
|
|
/// [css-align-overflow]: https://www.w3.org/TR/css-align-3/#overflow-values
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[repr(u8)]
|
|
pub enum AlignmentSafety {
|
|
/// Default — keeps the requested alignment even when the subject overflows the
|
|
/// alignment container at the start edge.
|
|
Unsafe,
|
|
/// Falls back to the start edge when the subject would overflow, to avoid data
|
|
/// loss.
|
|
Safe,
|
|
}
|
|
|
|
/// Used to control how child nodes are aligned.
|
|
/// For Flexbox it controls alignment in the cross axis.
|
|
/// For Grid it controls alignment in the block axis.
|
|
///
|
|
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
pub struct AlignItems {
|
|
/// Position keyword.
|
|
pub keyword: AlignItemsKeyword,
|
|
/// Overflow-position modifier (`safe` / `unsafe`).
|
|
pub safety: AlignmentSafety,
|
|
}
|
|
|
|
impl AlignItems {
|
|
/// Items are packed toward the start of the axis.
|
|
pub const START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed toward the end of the axis.
|
|
pub const END: Self = Self { keyword: AlignItemsKeyword::End, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed towards the flex-relative start of the axis.
|
|
pub const FLEX_START: Self = Self { keyword: AlignItemsKeyword::FlexStart, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed towards the flex-relative end of the axis.
|
|
pub const FLEX_END: Self = Self { keyword: AlignItemsKeyword::FlexEnd, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed along the center of the cross axis.
|
|
pub const CENTER: Self = Self { keyword: AlignItemsKeyword::Center, safety: AlignmentSafety::Unsafe };
|
|
/// Items are aligned such as their baselines align.
|
|
pub const BASELINE: Self = Self { keyword: AlignItemsKeyword::Baseline, safety: AlignmentSafety::Unsafe };
|
|
/// Stretch to fill the container.
|
|
pub const STRETCH: Self = Self { keyword: AlignItemsKeyword::Stretch, safety: AlignmentSafety::Unsafe };
|
|
/// Like [`AlignItems::START`], but falls back to [`AlignItems::START`] when the
|
|
/// alignment subject overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignItems::END`], but falls back to [`AlignItems::START`] when the
|
|
/// alignment subject overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_END: Self = Self { keyword: AlignItemsKeyword::End, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignItems::FLEX_START`], but falls back to [`AlignItems::START`] when the
|
|
/// alignment subject overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_FLEX_START: Self = Self { keyword: AlignItemsKeyword::FlexStart, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignItems::FLEX_END`], but falls back to [`AlignItems::START`] when the
|
|
/// alignment subject overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_FLEX_END: Self = Self { keyword: AlignItemsKeyword::FlexEnd, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignItems::CENTER`], but falls back to [`AlignItems::START`] when the
|
|
/// alignment subject overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_CENTER: Self = Self { keyword: AlignItemsKeyword::Center, safety: AlignmentSafety::Safe };
|
|
|
|
/// Returns `true` iff this carries the `safe` overflow-position modifier.
|
|
#[inline]
|
|
pub const fn is_safe(self) -> bool {
|
|
matches!(self.safety, AlignmentSafety::Safe)
|
|
}
|
|
|
|
/// Returns the underlying position keyword, discarding the safety modifier.
|
|
#[inline]
|
|
pub const fn keyword(self) -> AlignItemsKeyword {
|
|
self.keyword
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
impl FromCss for AlignItems {
|
|
fn from_css<'i>(input: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
|
|
let first = input.expect_ident()?.clone();
|
|
cssparser::match_ignore_ascii_case! { &*first,
|
|
"safe" => {
|
|
let pos = input.expect_ident()?.clone();
|
|
cssparser::match_ignore_ascii_case! { &*pos,
|
|
"start" => Ok(Self::SAFE_START),
|
|
"end" => Ok(Self::SAFE_END),
|
|
"flex-start" => Ok(Self::SAFE_FLEX_START),
|
|
"flex-end" => Ok(Self::SAFE_FLEX_END),
|
|
"center" => Ok(Self::SAFE_CENTER),
|
|
_ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
|
|
}
|
|
},
|
|
"unsafe" => {
|
|
let pos = input.expect_ident()?.clone();
|
|
cssparser::match_ignore_ascii_case! { &*pos,
|
|
"start" => Ok(Self::START),
|
|
"end" => Ok(Self::END),
|
|
"flex-start" => Ok(Self::FLEX_START),
|
|
"flex-end" => Ok(Self::FLEX_END),
|
|
"center" => Ok(Self::CENTER),
|
|
_ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
|
|
}
|
|
},
|
|
"start" => Ok(Self::START),
|
|
"end" => Ok(Self::END),
|
|
"flex-start" => Ok(Self::FLEX_START),
|
|
"flex-end" => Ok(Self::FLEX_END),
|
|
"center" => Ok(Self::CENTER),
|
|
"baseline" => Ok(Self::BASELINE),
|
|
"stretch" => Ok(Self::STRETCH),
|
|
_ => Err(input.new_unexpected_token_error(Token::Ident(first))),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
crate::util::parse::from_str_from_css!(AlignItems);
|
|
|
|
/// Used to control how child nodes are aligned.
|
|
/// Does not apply to Flexbox, and will be ignored if specified on a flex container.
|
|
/// For Grid it controls alignment in the inline axis.
|
|
///
|
|
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items)
|
|
pub type JustifyItems = AlignItems;
|
|
/// Controls alignment of an individual node.
|
|
///
|
|
/// Overrides the parent Node's `AlignItems` property.
|
|
/// For Flexbox it controls alignment in the cross axis.
|
|
/// For Grid it controls alignment in the block axis.
|
|
///
|
|
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-self)
|
|
pub type AlignSelf = AlignItems;
|
|
/// Controls alignment of an individual node.
|
|
///
|
|
/// Overrides the parent Node's `JustifyItems` property.
|
|
/// Does not apply to Flexbox, and will be ignored if specified on a flex child.
|
|
/// For Grid it controls alignment in the inline axis.
|
|
///
|
|
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self)
|
|
pub type JustifySelf = AlignItems;
|
|
|
|
/// Sets the distribution of space between and around content items.
|
|
/// For Flexbox it controls alignment in the cross axis.
|
|
/// For Grid it controls alignment in the block axis.
|
|
///
|
|
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content)
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
pub struct AlignContent {
|
|
/// Position keyword.
|
|
pub keyword: AlignContentKeyword,
|
|
/// Overflow-position modifier (`safe` / `unsafe`).
|
|
pub safety: AlignmentSafety,
|
|
}
|
|
|
|
impl AlignContent {
|
|
/// Items are packed toward the start of the axis.
|
|
pub const START: Self = Self { keyword: AlignContentKeyword::Start, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed toward the end of the axis.
|
|
pub const END: Self = Self { keyword: AlignContentKeyword::End, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed towards the flex-relative start of the axis.
|
|
pub const FLEX_START: Self = Self { keyword: AlignContentKeyword::FlexStart, safety: AlignmentSafety::Unsafe };
|
|
/// Items are packed towards the flex-relative end of the axis.
|
|
pub const FLEX_END: Self = Self { keyword: AlignContentKeyword::FlexEnd, safety: AlignmentSafety::Unsafe };
|
|
/// Items are centered around the middle of the axis.
|
|
pub const CENTER: Self = Self { keyword: AlignContentKeyword::Center, safety: AlignmentSafety::Unsafe };
|
|
/// Items are stretched to fill the container.
|
|
pub const STRETCH: Self = Self { keyword: AlignContentKeyword::Stretch, safety: AlignmentSafety::Unsafe };
|
|
/// The first and last items are aligned flush with the edges of the container.
|
|
pub const SPACE_BETWEEN: Self =
|
|
Self { keyword: AlignContentKeyword::SpaceBetween, safety: AlignmentSafety::Unsafe };
|
|
/// The gap between the first and last items equals the gap between items.
|
|
pub const SPACE_EVENLY: Self = Self { keyword: AlignContentKeyword::SpaceEvenly, safety: AlignmentSafety::Unsafe };
|
|
/// The gap between the first and last items is half the gap between items.
|
|
pub const SPACE_AROUND: Self = Self { keyword: AlignContentKeyword::SpaceAround, safety: AlignmentSafety::Unsafe };
|
|
/// Like [`AlignContent::START`], but falls back to [`AlignContent::START`] when the
|
|
/// content overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_START: Self = Self { keyword: AlignContentKeyword::Start, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignContent::END`], but falls back to [`AlignContent::START`] when the
|
|
/// content overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_END: Self = Self { keyword: AlignContentKeyword::End, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignContent::FLEX_START`], but falls back to [`AlignContent::START`] when
|
|
/// the content overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_FLEX_START: Self = Self { keyword: AlignContentKeyword::FlexStart, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignContent::FLEX_END`], but falls back to [`AlignContent::START`] when the
|
|
/// content overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_FLEX_END: Self = Self { keyword: AlignContentKeyword::FlexEnd, safety: AlignmentSafety::Safe };
|
|
/// Like [`AlignContent::CENTER`], but falls back to [`AlignContent::START`] when the
|
|
/// content overflows the alignment container, to avoid data loss.
|
|
pub const SAFE_CENTER: Self = Self { keyword: AlignContentKeyword::Center, safety: AlignmentSafety::Safe };
|
|
|
|
/// Returns `true` iff this carries the `safe` overflow-position modifier.
|
|
#[inline]
|
|
pub const fn is_safe(self) -> bool {
|
|
matches!(self.safety, AlignmentSafety::Safe)
|
|
}
|
|
|
|
/// Returns the underlying position keyword, discarding the safety modifier.
|
|
#[inline]
|
|
pub const fn keyword(self) -> AlignContentKeyword {
|
|
self.keyword
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
impl FromCss for AlignContent {
|
|
fn from_css<'i>(input: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
|
|
let first = input.expect_ident()?.clone();
|
|
cssparser::match_ignore_ascii_case! { &*first,
|
|
"safe" => {
|
|
let pos = input.expect_ident()?.clone();
|
|
cssparser::match_ignore_ascii_case! { &*pos,
|
|
"start" => Ok(Self::SAFE_START),
|
|
"end" => Ok(Self::SAFE_END),
|
|
"flex-start" => Ok(Self::SAFE_FLEX_START),
|
|
"flex-end" => Ok(Self::SAFE_FLEX_END),
|
|
"center" => Ok(Self::SAFE_CENTER),
|
|
_ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
|
|
}
|
|
},
|
|
"unsafe" => {
|
|
let pos = input.expect_ident()?.clone();
|
|
cssparser::match_ignore_ascii_case! { &*pos,
|
|
"start" => Ok(Self::START),
|
|
"end" => Ok(Self::END),
|
|
"flex-start" => Ok(Self::FLEX_START),
|
|
"flex-end" => Ok(Self::FLEX_END),
|
|
"center" => Ok(Self::CENTER),
|
|
_ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
|
|
}
|
|
},
|
|
"start" => Ok(Self::START),
|
|
"end" => Ok(Self::END),
|
|
"flex-start" => Ok(Self::FLEX_START),
|
|
"flex-end" => Ok(Self::FLEX_END),
|
|
"center" => Ok(Self::CENTER),
|
|
"stretch" => Ok(Self::STRETCH),
|
|
"space-between" => Ok(Self::SPACE_BETWEEN),
|
|
"space-evenly" => Ok(Self::SPACE_EVENLY),
|
|
"space-around" => Ok(Self::SPACE_AROUND),
|
|
_ => Err(input.new_unexpected_token_error(Token::Ident(first))),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
crate::util::parse::from_str_from_css!(AlignContent);
|
|
|
|
/// Sets the distribution of space between and around content items.
|
|
/// For Flexbox it controls alignment in the main axis.
|
|
/// For Grid it controls alignment in the inline axis.
|
|
///
|
|
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content)
|
|
pub type JustifyContent = AlignContent;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Serde — custom impls preserve the pre-struct wire format (single tag string
|
|
// per public spelling) so consumers reading data serialized before the refactor
|
|
// continue to deserialize correctly.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Canonical tag-string set accepted by [`AlignItems`] serde deserialization, used in
|
|
/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
|
|
#[cfg(feature = "serde")]
|
|
const ALIGN_ITEMS_NAMES: &[&str] = &[
|
|
"Start",
|
|
"End",
|
|
"FlexStart",
|
|
"FlexEnd",
|
|
"Center",
|
|
"Baseline",
|
|
"Stretch",
|
|
"SafeStart",
|
|
"SafeEnd",
|
|
"SafeFlexStart",
|
|
"SafeFlexEnd",
|
|
"SafeCenter",
|
|
];
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl serde::Serialize for AlignItems {
|
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
let name = match (self.keyword, self.safety) {
|
|
(AlignItemsKeyword::Start, AlignmentSafety::Unsafe) => "Start",
|
|
(AlignItemsKeyword::End, AlignmentSafety::Unsafe) => "End",
|
|
(AlignItemsKeyword::FlexStart, AlignmentSafety::Unsafe) => "FlexStart",
|
|
(AlignItemsKeyword::FlexEnd, AlignmentSafety::Unsafe) => "FlexEnd",
|
|
(AlignItemsKeyword::Center, AlignmentSafety::Unsafe) => "Center",
|
|
(AlignItemsKeyword::Baseline, _) => "Baseline",
|
|
(AlignItemsKeyword::Stretch, _) => "Stretch",
|
|
(AlignItemsKeyword::Start, AlignmentSafety::Safe) => "SafeStart",
|
|
(AlignItemsKeyword::End, AlignmentSafety::Safe) => "SafeEnd",
|
|
(AlignItemsKeyword::FlexStart, AlignmentSafety::Safe) => "SafeFlexStart",
|
|
(AlignItemsKeyword::FlexEnd, AlignmentSafety::Safe) => "SafeFlexEnd",
|
|
(AlignItemsKeyword::Center, AlignmentSafety::Safe) => "SafeCenter",
|
|
};
|
|
serializer.serialize_str(name)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> serde::Deserialize<'de> for AlignItems {
|
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
struct AlignItemsVisitor;
|
|
impl<'de> serde::de::Visitor<'de> for AlignItemsVisitor {
|
|
type Value = AlignItems;
|
|
fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
fmt.write_str("an AlignItems variant tag string")
|
|
}
|
|
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
|
Ok(match v {
|
|
"Start" => AlignItems::START,
|
|
"End" => AlignItems::END,
|
|
"FlexStart" => AlignItems::FLEX_START,
|
|
"FlexEnd" => AlignItems::FLEX_END,
|
|
"Center" => AlignItems::CENTER,
|
|
"Baseline" => AlignItems::BASELINE,
|
|
"Stretch" => AlignItems::STRETCH,
|
|
"SafeStart" => AlignItems::SAFE_START,
|
|
"SafeEnd" => AlignItems::SAFE_END,
|
|
"SafeFlexStart" => AlignItems::SAFE_FLEX_START,
|
|
"SafeFlexEnd" => AlignItems::SAFE_FLEX_END,
|
|
"SafeCenter" => AlignItems::SAFE_CENTER,
|
|
other => return Err(E::unknown_variant(other, ALIGN_ITEMS_NAMES)),
|
|
})
|
|
}
|
|
}
|
|
deserializer.deserialize_str(AlignItemsVisitor)
|
|
}
|
|
}
|
|
|
|
/// Canonical tag-string set accepted by [`AlignContent`] serde deserialization, used in
|
|
/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
|
|
#[cfg(feature = "serde")]
|
|
const ALIGN_CONTENT_NAMES: &[&str] = &[
|
|
"Start",
|
|
"End",
|
|
"FlexStart",
|
|
"FlexEnd",
|
|
"Center",
|
|
"Stretch",
|
|
"SpaceBetween",
|
|
"SpaceEvenly",
|
|
"SpaceAround",
|
|
"SafeStart",
|
|
"SafeEnd",
|
|
"SafeFlexStart",
|
|
"SafeFlexEnd",
|
|
"SafeCenter",
|
|
];
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl serde::Serialize for AlignContent {
|
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
let name = match (self.keyword, self.safety) {
|
|
(AlignContentKeyword::Start, AlignmentSafety::Unsafe) => "Start",
|
|
(AlignContentKeyword::End, AlignmentSafety::Unsafe) => "End",
|
|
(AlignContentKeyword::FlexStart, AlignmentSafety::Unsafe) => "FlexStart",
|
|
(AlignContentKeyword::FlexEnd, AlignmentSafety::Unsafe) => "FlexEnd",
|
|
(AlignContentKeyword::Center, AlignmentSafety::Unsafe) => "Center",
|
|
(AlignContentKeyword::Stretch, _) => "Stretch",
|
|
(AlignContentKeyword::SpaceBetween, _) => "SpaceBetween",
|
|
(AlignContentKeyword::SpaceEvenly, _) => "SpaceEvenly",
|
|
(AlignContentKeyword::SpaceAround, _) => "SpaceAround",
|
|
(AlignContentKeyword::Start, AlignmentSafety::Safe) => "SafeStart",
|
|
(AlignContentKeyword::End, AlignmentSafety::Safe) => "SafeEnd",
|
|
(AlignContentKeyword::FlexStart, AlignmentSafety::Safe) => "SafeFlexStart",
|
|
(AlignContentKeyword::FlexEnd, AlignmentSafety::Safe) => "SafeFlexEnd",
|
|
(AlignContentKeyword::Center, AlignmentSafety::Safe) => "SafeCenter",
|
|
};
|
|
serializer.serialize_str(name)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> serde::Deserialize<'de> for AlignContent {
|
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
|
struct AlignContentVisitor;
|
|
impl<'de> serde::de::Visitor<'de> for AlignContentVisitor {
|
|
type Value = AlignContent;
|
|
fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
fmt.write_str("an AlignContent variant tag string")
|
|
}
|
|
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
|
Ok(match v {
|
|
"Start" => AlignContent::START,
|
|
"End" => AlignContent::END,
|
|
"FlexStart" => AlignContent::FLEX_START,
|
|
"FlexEnd" => AlignContent::FLEX_END,
|
|
"Center" => AlignContent::CENTER,
|
|
"Stretch" => AlignContent::STRETCH,
|
|
"SpaceBetween" => AlignContent::SPACE_BETWEEN,
|
|
"SpaceEvenly" => AlignContent::SPACE_EVENLY,
|
|
"SpaceAround" => AlignContent::SPACE_AROUND,
|
|
"SafeStart" => AlignContent::SAFE_START,
|
|
"SafeEnd" => AlignContent::SAFE_END,
|
|
"SafeFlexStart" => AlignContent::SAFE_FLEX_START,
|
|
"SafeFlexEnd" => AlignContent::SAFE_FLEX_END,
|
|
"SafeCenter" => AlignContent::SAFE_CENTER,
|
|
other => return Err(E::unknown_variant(other, ALIGN_CONTENT_NAMES)),
|
|
})
|
|
}
|
|
}
|
|
deserializer.deserialize_str(AlignContentVisitor)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use core::mem::size_of;
|
|
|
|
// Size budget — struct = 1B keyword + 1B safety, no niche packing.
|
|
// Pre-refactor each was a single-byte enum; spec §V13 caps regression at +2B.
|
|
#[test]
|
|
fn align_types_within_size_budget() {
|
|
assert!(size_of::<AlignItems>() <= 2, "AlignItems grew to {}", size_of::<AlignItems>());
|
|
assert!(size_of::<AlignContent>() <= 2, "AlignContent grew to {}", size_of::<AlignContent>());
|
|
assert!(size_of::<Option<AlignItems>>() <= 3);
|
|
assert!(size_of::<Option<AlignContent>>() <= 3);
|
|
}
|
|
|
|
#[test]
|
|
fn align_items_is_safe() {
|
|
assert!(AlignItems::SAFE_START.is_safe());
|
|
assert!(AlignItems::SAFE_END.is_safe());
|
|
assert!(AlignItems::SAFE_FLEX_START.is_safe());
|
|
assert!(AlignItems::SAFE_FLEX_END.is_safe());
|
|
assert!(AlignItems::SAFE_CENTER.is_safe());
|
|
assert!(!AlignItems::START.is_safe());
|
|
assert!(!AlignItems::END.is_safe());
|
|
assert!(!AlignItems::FLEX_START.is_safe());
|
|
assert!(!AlignItems::FLEX_END.is_safe());
|
|
assert!(!AlignItems::CENTER.is_safe());
|
|
assert!(!AlignItems::BASELINE.is_safe());
|
|
assert!(!AlignItems::STRETCH.is_safe());
|
|
}
|
|
|
|
#[test]
|
|
fn align_items_keyword_strips_safe() {
|
|
assert_eq!(AlignItems::SAFE_START.keyword(), AlignItemsKeyword::Start);
|
|
assert_eq!(AlignItems::SAFE_END.keyword(), AlignItemsKeyword::End);
|
|
assert_eq!(AlignItems::SAFE_FLEX_START.keyword(), AlignItemsKeyword::FlexStart);
|
|
assert_eq!(AlignItems::SAFE_FLEX_END.keyword(), AlignItemsKeyword::FlexEnd);
|
|
assert_eq!(AlignItems::SAFE_CENTER.keyword(), AlignItemsKeyword::Center);
|
|
}
|
|
|
|
#[test]
|
|
fn align_items_keyword_passthrough() {
|
|
assert_eq!(AlignItems::START.keyword(), AlignItemsKeyword::Start);
|
|
assert_eq!(AlignItems::STRETCH.keyword(), AlignItemsKeyword::Stretch);
|
|
assert_eq!(AlignItems::BASELINE.keyword(), AlignItemsKeyword::Baseline);
|
|
assert_eq!(AlignItems::FLEX_START.keyword(), AlignItemsKeyword::FlexStart);
|
|
}
|
|
|
|
#[test]
|
|
fn align_content_is_safe() {
|
|
assert!(AlignContent::SAFE_START.is_safe());
|
|
assert!(AlignContent::SAFE_CENTER.is_safe());
|
|
assert!(!AlignContent::SPACE_BETWEEN.is_safe());
|
|
assert!(!AlignContent::STRETCH.is_safe());
|
|
}
|
|
|
|
#[test]
|
|
fn align_content_keyword_strips_safe() {
|
|
assert_eq!(AlignContent::SAFE_START.keyword(), AlignContentKeyword::Start);
|
|
assert_eq!(AlignContent::SAFE_FLEX_END.keyword(), AlignContentKeyword::FlexEnd);
|
|
assert_eq!(AlignContent::SAFE_CENTER.keyword(), AlignContentKeyword::Center);
|
|
assert_eq!(AlignContent::SPACE_BETWEEN.keyword(), AlignContentKeyword::SpaceBetween);
|
|
}
|
|
|
|
#[test]
|
|
fn align_content_keyword_reversed_swaps_start_end() {
|
|
assert_eq!(AlignContentKeyword::Start.reversed(), AlignContentKeyword::End);
|
|
assert_eq!(AlignContentKeyword::End.reversed(), AlignContentKeyword::Start);
|
|
assert_eq!(AlignContentKeyword::FlexStart.reversed(), AlignContentKeyword::FlexEnd);
|
|
assert_eq!(AlignContentKeyword::FlexEnd.reversed(), AlignContentKeyword::FlexStart);
|
|
// Stretch reverses to End — preserves pre-refactor behaviour.
|
|
assert_eq!(AlignContentKeyword::Stretch.reversed(), AlignContentKeyword::End);
|
|
assert_eq!(AlignContentKeyword::Center.reversed(), AlignContentKeyword::Center);
|
|
assert_eq!(AlignContentKeyword::SpaceBetween.reversed(), AlignContentKeyword::SpaceBetween);
|
|
assert_eq!(AlignContentKeyword::SpaceEvenly.reversed(), AlignContentKeyword::SpaceEvenly);
|
|
assert_eq!(AlignContentKeyword::SpaceAround.reversed(), AlignContentKeyword::SpaceAround);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_items_plain() {
|
|
assert_eq!("start".parse::<AlignItems>().unwrap(), AlignItems::START);
|
|
assert_eq!("end".parse::<AlignItems>().unwrap(), AlignItems::END);
|
|
assert_eq!("flex-start".parse::<AlignItems>().unwrap(), AlignItems::FLEX_START);
|
|
assert_eq!("flex-end".parse::<AlignItems>().unwrap(), AlignItems::FLEX_END);
|
|
assert_eq!("center".parse::<AlignItems>().unwrap(), AlignItems::CENTER);
|
|
assert_eq!("baseline".parse::<AlignItems>().unwrap(), AlignItems::BASELINE);
|
|
assert_eq!("stretch".parse::<AlignItems>().unwrap(), AlignItems::STRETCH);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_items_safe() {
|
|
assert_eq!("safe start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_START);
|
|
assert_eq!("safe end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_END);
|
|
assert_eq!("safe flex-start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_START);
|
|
assert_eq!("safe flex-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_END);
|
|
assert_eq!("safe center".parse::<AlignItems>().unwrap(), AlignItems::SAFE_CENTER);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_items_safe_case_insensitive() {
|
|
assert_eq!("SAFE Start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_START);
|
|
assert_eq!("Safe FLEX-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_END);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_items_unsafe_drops_modifier() {
|
|
assert_eq!("unsafe start".parse::<AlignItems>().unwrap(), AlignItems::START);
|
|
assert_eq!("unsafe end".parse::<AlignItems>().unwrap(), AlignItems::END);
|
|
assert_eq!("unsafe center".parse::<AlignItems>().unwrap(), AlignItems::CENTER);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_items_rejects_invalid_safe_combos() {
|
|
assert!("safe stretch".parse::<AlignItems>().is_err());
|
|
assert!("safe baseline".parse::<AlignItems>().is_err());
|
|
assert!("safe space-between".parse::<AlignItems>().is_err());
|
|
assert!("safe".parse::<AlignItems>().is_err());
|
|
assert!("safe garbage".parse::<AlignItems>().is_err());
|
|
assert!("unsafe stretch".parse::<AlignItems>().is_err());
|
|
assert!("unsafe baseline".parse::<AlignItems>().is_err());
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_content_plain() {
|
|
assert_eq!("start".parse::<AlignContent>().unwrap(), AlignContent::START);
|
|
assert_eq!("space-between".parse::<AlignContent>().unwrap(), AlignContent::SPACE_BETWEEN);
|
|
assert_eq!("space-evenly".parse::<AlignContent>().unwrap(), AlignContent::SPACE_EVENLY);
|
|
assert_eq!("space-around".parse::<AlignContent>().unwrap(), AlignContent::SPACE_AROUND);
|
|
assert_eq!("stretch".parse::<AlignContent>().unwrap(), AlignContent::STRETCH);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_content_safe() {
|
|
assert_eq!("safe start".parse::<AlignContent>().unwrap(), AlignContent::SAFE_START);
|
|
assert_eq!("safe end".parse::<AlignContent>().unwrap(), AlignContent::SAFE_END);
|
|
assert_eq!("safe flex-start".parse::<AlignContent>().unwrap(), AlignContent::SAFE_FLEX_START);
|
|
assert_eq!("safe flex-end".parse::<AlignContent>().unwrap(), AlignContent::SAFE_FLEX_END);
|
|
assert_eq!("safe center".parse::<AlignContent>().unwrap(), AlignContent::SAFE_CENTER);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_content_unsafe_drops_modifier() {
|
|
assert_eq!("unsafe start".parse::<AlignContent>().unwrap(), AlignContent::START);
|
|
assert_eq!("unsafe flex-end".parse::<AlignContent>().unwrap(), AlignContent::FLEX_END);
|
|
}
|
|
|
|
#[cfg(feature = "parse")]
|
|
#[test]
|
|
fn parse_align_content_rejects_invalid_safe_combos() {
|
|
assert!("safe stretch".parse::<AlignContent>().is_err());
|
|
assert!("safe space-between".parse::<AlignContent>().is_err());
|
|
assert!("safe space-evenly".parse::<AlignContent>().is_err());
|
|
assert!("safe space-around".parse::<AlignContent>().is_err());
|
|
assert!("safe".parse::<AlignContent>().is_err());
|
|
assert!("unsafe stretch".parse::<AlignContent>().is_err());
|
|
assert!("unsafe space-between".parse::<AlignContent>().is_err());
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
#[test]
|
|
fn serde_align_items_round_trip() {
|
|
let cases = [
|
|
(AlignItems::START, "\"Start\""),
|
|
(AlignItems::END, "\"End\""),
|
|
(AlignItems::FLEX_START, "\"FlexStart\""),
|
|
(AlignItems::FLEX_END, "\"FlexEnd\""),
|
|
(AlignItems::CENTER, "\"Center\""),
|
|
(AlignItems::BASELINE, "\"Baseline\""),
|
|
(AlignItems::STRETCH, "\"Stretch\""),
|
|
(AlignItems::SAFE_START, "\"SafeStart\""),
|
|
(AlignItems::SAFE_END, "\"SafeEnd\""),
|
|
(AlignItems::SAFE_FLEX_START, "\"SafeFlexStart\""),
|
|
(AlignItems::SAFE_FLEX_END, "\"SafeFlexEnd\""),
|
|
(AlignItems::SAFE_CENTER, "\"SafeCenter\""),
|
|
];
|
|
for (value, expected) in cases {
|
|
let serialized = serde_json::to_string(&value).unwrap();
|
|
assert_eq!(serialized, expected, "serialize {:?}", value);
|
|
let deserialized: AlignItems = serde_json::from_str(expected).unwrap();
|
|
assert_eq!(deserialized, value, "round-trip {:?}", value);
|
|
}
|
|
assert!(serde_json::from_str::<AlignItems>("\"NotAVariant\"").is_err());
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
#[test]
|
|
fn serde_align_content_round_trip() {
|
|
let cases = [
|
|
(AlignContent::START, "\"Start\""),
|
|
(AlignContent::END, "\"End\""),
|
|
(AlignContent::FLEX_START, "\"FlexStart\""),
|
|
(AlignContent::FLEX_END, "\"FlexEnd\""),
|
|
(AlignContent::CENTER, "\"Center\""),
|
|
(AlignContent::STRETCH, "\"Stretch\""),
|
|
(AlignContent::SPACE_BETWEEN, "\"SpaceBetween\""),
|
|
(AlignContent::SPACE_EVENLY, "\"SpaceEvenly\""),
|
|
(AlignContent::SPACE_AROUND, "\"SpaceAround\""),
|
|
(AlignContent::SAFE_START, "\"SafeStart\""),
|
|
(AlignContent::SAFE_END, "\"SafeEnd\""),
|
|
(AlignContent::SAFE_FLEX_START, "\"SafeFlexStart\""),
|
|
(AlignContent::SAFE_FLEX_END, "\"SafeFlexEnd\""),
|
|
(AlignContent::SAFE_CENTER, "\"SafeCenter\""),
|
|
];
|
|
for (value, expected) in cases {
|
|
let serialized = serde_json::to_string(&value).unwrap();
|
|
assert_eq!(serialized, expected, "serialize {:?}", value);
|
|
let deserialized: AlignContent = serde_json::from_str(expected).unwrap();
|
|
assert_eq!(deserialized, value, "round-trip {:?}", value);
|
|
}
|
|
assert!(serde_json::from_str::<AlignContent>("\"NotAVariant\"").is_err());
|
|
}
|
|
}
|