⎇ devin/1784819829-clamp-overlarge-grids
src/style/alignment.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Use SCREAMING_SNAKE_CASE for alignment constants (#961)
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
14/// The position-keyword half of [`AlignItems`] (and its aliases `AlignSelf`,
15/// `JustifyItems`, `JustifySelf`).
16///
17/// Compute paths match on this enum directly so every match is exhaustive and
18/// requires no `Safe*` siblings.
19#[derive(Copy, Clone, PartialEq, Eq, Debug)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[repr(u8)]
22pub enum AlignItemsKeyword {
23 /// Items are packed toward the start of the axis.
24 Start,
25 /// Items are packed toward the end of the axis.
26 End,
27 /// Items are packed towards the flex-relative start of the axis.
28 ///
29 /// For flex containers with flex_direction RowReverse or ColumnReverse this is
30 /// equivalent to End. In all other cases it is equivalent to Start.
31 FlexStart,
32 /// Items are packed towards the flex-relative end of the axis.
33 ///
34 /// For flex containers with flex_direction RowReverse or ColumnReverse this is
35 /// equivalent to Start. In all other cases it is equivalent to End.
36 FlexEnd,
37 /// Items are packed along the center of the cross axis.
38 Center,
39 /// Items are aligned such as their baselines align.
40 Baseline,
41 /// Stretch to fill the container.
42 Stretch,
43}
44
45/// The position-keyword half of [`AlignContent`] (and its alias `JustifyContent`).
46///
47/// Compute paths match on this enum directly so every match is exhaustive and
48/// requires no `Safe*` siblings.
49#[derive(Copy, Clone, PartialEq, Eq, Debug)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51#[repr(u8)]
52pub enum AlignContentKeyword {
53 /// Items are packed toward the start of the axis.
54 Start,
55 /// Items are packed toward the end of the axis.
56 End,
57 /// Items are packed towards the flex-relative start of the axis.
58 FlexStart,
59 /// Items are packed towards the flex-relative end of the axis.
60 FlexEnd,
61 /// Items are centered around the middle of the axis.
62 Center,
63 /// Items are stretched to fill the container.
64 Stretch,
65 /// The first and last items are aligned flush with the edges of the container
66 /// (no gap). The gap between items is distributed evenly.
67 SpaceBetween,
68 /// The gap between the first and last items is exactly THE SAME as the gap
69 /// between items. The gaps are distributed evenly.
70 SpaceEvenly,
71 /// The gap between the first and last items is exactly HALF the gap between
72 /// items. The gaps are distributed evenly in proportion to these ratios.
73 SpaceAround,
74}
75
76impl AlignContentKeyword {
77 /// Returns the reversed keyword for RTL (right-to-left) contexts: `Start`↔`End`,
78 /// `FlexStart`↔`FlexEnd`. `Stretch` maps to `End` to preserve the layout
79 /// algorithms' historical handling. Center and the distribution keywords
80 /// (`SpaceBetween`, `SpaceEvenly`, `SpaceAround`) are unaffected because their
81 /// visual placement is direction-symmetric.
82 pub(crate) fn reversed(self) -> Self {
83 match self {
84 Self::Start => Self::End,
85 Self::End => Self::Start,
86 Self::FlexStart => Self::FlexEnd,
87 Self::FlexEnd => Self::FlexStart,
88 Self::Stretch => Self::End,
89 Self::Center | Self::SpaceBetween | Self::SpaceEvenly | Self::SpaceAround => self,
90 }
91 }
92}
93
94/// The overflow-position modifier per [CSS Box Alignment §4.3][css-align-overflow].
95///
96/// `Safe` falls back to start-edge alignment when the alignment subject would
97/// overflow the alignment container, so the start of the content stays visible.
98/// `Unsafe` (the default) keeps the requested alignment even when that causes
99/// overflow at the start edge.
100///
101/// CSS only defines `safe` / `unsafe` against the position values `start`, `end`,
102/// `flex-start`, `flex-end`, `center`. The struct shape does not enforce that
103/// constraint at the type level — the parser rejects invalid combinations, and
104/// the compute pass treats `Safe` paired with a non-position keyword (`Stretch`,
105/// `Baseline`, `Space*`) the same as `Unsafe`.
106///
107/// [css-align-overflow]: https://www.w3.org/TR/css-align-3/#overflow-values
108#[derive(Copy, Clone, PartialEq, Eq, Debug)]
109#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
110#[repr(u8)]
111pub enum AlignmentSafety {
112 /// Default — keeps the requested alignment even when the subject overflows the
113 /// alignment container at the start edge.
114 Unsafe,
115 /// Falls back to the start edge when the subject would overflow, to avoid data
116 /// loss.
117 Safe,
118}
119
120/// Used to control how child nodes are aligned.
121/// For Flexbox it controls alignment in the cross axis.
122/// For Grid it controls alignment in the block axis.
123///
124/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
125#[derive(Copy, Clone, PartialEq, Eq, Debug)]
126pub struct AlignItems {
127 /// Position keyword.
128 pub keyword: AlignItemsKeyword,
129 /// Overflow-position modifier (`safe` / `unsafe`).
130 pub safety: AlignmentSafety,
131}
132
133impl AlignItems {
134 /// Items are packed toward the start of the axis.
135 pub const START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Unsafe };
136 /// Items are packed toward the end of the axis.
137 pub const END: Self = Self { keyword: AlignItemsKeyword::End, safety: AlignmentSafety::Unsafe };
138 /// Items are packed towards the flex-relative start of the axis.
139 pub const FLEX_START: Self = Self { keyword: AlignItemsKeyword::FlexStart, safety: AlignmentSafety::Unsafe };
140 /// Items are packed towards the flex-relative end of the axis.
141 pub const FLEX_END: Self = Self { keyword: AlignItemsKeyword::FlexEnd, safety: AlignmentSafety::Unsafe };
142 /// Items are packed along the center of the cross axis.
143 pub const CENTER: Self = Self { keyword: AlignItemsKeyword::Center, safety: AlignmentSafety::Unsafe };
144 /// Items are aligned such as their baselines align.
145 pub const BASELINE: Self = Self { keyword: AlignItemsKeyword::Baseline, safety: AlignmentSafety::Unsafe };
146 /// Stretch to fill the container.
147 pub const STRETCH: Self = Self { keyword: AlignItemsKeyword::Stretch, safety: AlignmentSafety::Unsafe };
148 /// Like [`AlignItems::START`], but falls back to [`AlignItems::START`] when the
149 /// alignment subject overflows the alignment container, to avoid data loss.
150 pub const SAFE_START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Safe };
151 /// Like [`AlignItems::END`], but falls back to [`AlignItems::START`] when the
152 /// alignment subject overflows the alignment container, to avoid data loss.
153 pub const SAFE_END: Self = Self { keyword: AlignItemsKeyword::End, safety: AlignmentSafety::Safe };
154 /// Like [`AlignItems::FLEX_START`], but falls back to [`AlignItems::START`] when the
155 /// alignment subject overflows the alignment container, to avoid data loss.
156 pub const SAFE_FLEX_START: Self = Self { keyword: AlignItemsKeyword::FlexStart, safety: AlignmentSafety::Safe };
157 /// Like [`AlignItems::FLEX_END`], but falls back to [`AlignItems::START`] when the
158 /// alignment subject overflows the alignment container, to avoid data loss.
159 pub const SAFE_FLEX_END: Self = Self { keyword: AlignItemsKeyword::FlexEnd, safety: AlignmentSafety::Safe };
160 /// Like [`AlignItems::CENTER`], but falls back to [`AlignItems::START`] when the
161 /// alignment subject overflows the alignment container, to avoid data loss.
162 pub const SAFE_CENTER: Self = Self { keyword: AlignItemsKeyword::Center, safety: AlignmentSafety::Safe };
163
164 /// Returns `true` iff this carries the `safe` overflow-position modifier.
165 #[inline]
166 pub const fn is_safe(self) -> bool {
167 matches!(self.safety, AlignmentSafety::Safe)
168 }
169
170 /// Returns the underlying position keyword, discarding the safety modifier.
171 #[inline]
172 pub const fn keyword(self) -> AlignItemsKeyword {
173 self.keyword
174 }
175}
176
177#[cfg(feature = "parse")]
178impl FromCss for AlignItems {
179 fn from_css<'i>(input: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
180 let first = input.expect_ident()?.clone();
181 cssparser::match_ignore_ascii_case! { &*first,
182 "safe" => {
183 let pos = input.expect_ident()?.clone();
184 cssparser::match_ignore_ascii_case! { &*pos,
185 "start" => Ok(Self::SAFE_START),
186 "end" => Ok(Self::SAFE_END),
187 "flex-start" => Ok(Self::SAFE_FLEX_START),
188 "flex-end" => Ok(Self::SAFE_FLEX_END),
189 "center" => Ok(Self::SAFE_CENTER),
190 _ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
191 }
192 },
193 "unsafe" => {
194 let pos = input.expect_ident()?.clone();
195 cssparser::match_ignore_ascii_case! { &*pos,
196 "start" => Ok(Self::START),
197 "end" => Ok(Self::END),
198 "flex-start" => Ok(Self::FLEX_START),
199 "flex-end" => Ok(Self::FLEX_END),
200 "center" => Ok(Self::CENTER),
201 _ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
202 }
203 },
204 "start" => Ok(Self::START),
205 "end" => Ok(Self::END),
206 "flex-start" => Ok(Self::FLEX_START),
207 "flex-end" => Ok(Self::FLEX_END),
208 "center" => Ok(Self::CENTER),
209 "baseline" => Ok(Self::BASELINE),
210 "stretch" => Ok(Self::STRETCH),
211 _ => Err(input.new_unexpected_token_error(Token::Ident(first))),
212 }
213 }
214}
215
216#[cfg(feature = "parse")]
217crate::util::parse::from_str_from_css!(AlignItems);
218
219/// Used to control how child nodes are aligned.
220/// Does not apply to Flexbox, and will be ignored if specified on a flex container.
221/// For Grid it controls alignment in the inline axis.
222///
223/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items)
224pub type JustifyItems = AlignItems;
225/// Controls alignment of an individual node.
226///
227/// Overrides the parent Node's `AlignItems` property.
228/// For Flexbox it controls alignment in the cross axis.
229/// For Grid it controls alignment in the block axis.
230///
231/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-self)
232pub type AlignSelf = AlignItems;
233/// Controls alignment of an individual node.
234///
235/// Overrides the parent Node's `JustifyItems` property.
236/// Does not apply to Flexbox, and will be ignored if specified on a flex child.
237/// For Grid it controls alignment in the inline axis.
238///
239/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self)
240pub type JustifySelf = AlignItems;
241
242/// Sets the distribution of space between and around content items.
243/// For Flexbox it controls alignment in the cross axis.
244/// For Grid it controls alignment in the block axis.
245///
246/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content)
247#[derive(Copy, Clone, PartialEq, Eq, Debug)]
248pub struct AlignContent {
249 /// Position keyword.
250 pub keyword: AlignContentKeyword,
251 /// Overflow-position modifier (`safe` / `unsafe`).
252 pub safety: AlignmentSafety,
253}
254
255impl AlignContent {
256 /// Items are packed toward the start of the axis.
257 pub const START: Self = Self { keyword: AlignContentKeyword::Start, safety: AlignmentSafety::Unsafe };
258 /// Items are packed toward the end of the axis.
259 pub const END: Self = Self { keyword: AlignContentKeyword::End, safety: AlignmentSafety::Unsafe };
260 /// Items are packed towards the flex-relative start of the axis.
261 pub const FLEX_START: Self = Self { keyword: AlignContentKeyword::FlexStart, safety: AlignmentSafety::Unsafe };
262 /// Items are packed towards the flex-relative end of the axis.
263 pub const FLEX_END: Self = Self { keyword: AlignContentKeyword::FlexEnd, safety: AlignmentSafety::Unsafe };
264 /// Items are centered around the middle of the axis.
265 pub const CENTER: Self = Self { keyword: AlignContentKeyword::Center, safety: AlignmentSafety::Unsafe };
266 /// Items are stretched to fill the container.
267 pub const STRETCH: Self = Self { keyword: AlignContentKeyword::Stretch, safety: AlignmentSafety::Unsafe };
268 /// The first and last items are aligned flush with the edges of the container.
269 pub const SPACE_BETWEEN: Self =
270 Self { keyword: AlignContentKeyword::SpaceBetween, safety: AlignmentSafety::Unsafe };
271 /// The gap between the first and last items equals the gap between items.
272 pub const SPACE_EVENLY: Self = Self { keyword: AlignContentKeyword::SpaceEvenly, safety: AlignmentSafety::Unsafe };
273 /// The gap between the first and last items is half the gap between items.
274 pub const SPACE_AROUND: Self = Self { keyword: AlignContentKeyword::SpaceAround, safety: AlignmentSafety::Unsafe };
275 /// Like [`AlignContent::START`], but falls back to [`AlignContent::START`] when the
276 /// content overflows the alignment container, to avoid data loss.
277 pub const SAFE_START: Self = Self { keyword: AlignContentKeyword::Start, safety: AlignmentSafety::Safe };
278 /// Like [`AlignContent::END`], but falls back to [`AlignContent::START`] when the
279 /// content overflows the alignment container, to avoid data loss.
280 pub const SAFE_END: Self = Self { keyword: AlignContentKeyword::End, safety: AlignmentSafety::Safe };
281 /// Like [`AlignContent::FLEX_START`], but falls back to [`AlignContent::START`] when
282 /// the content overflows the alignment container, to avoid data loss.
283 pub const SAFE_FLEX_START: Self = Self { keyword: AlignContentKeyword::FlexStart, safety: AlignmentSafety::Safe };
284 /// Like [`AlignContent::FLEX_END`], but falls back to [`AlignContent::START`] when the
285 /// content overflows the alignment container, to avoid data loss.
286 pub const SAFE_FLEX_END: Self = Self { keyword: AlignContentKeyword::FlexEnd, safety: AlignmentSafety::Safe };
287 /// Like [`AlignContent::CENTER`], but falls back to [`AlignContent::START`] when the
288 /// content overflows the alignment container, to avoid data loss.
289 pub const SAFE_CENTER: Self = Self { keyword: AlignContentKeyword::Center, safety: AlignmentSafety::Safe };
290
291 /// Returns `true` iff this carries the `safe` overflow-position modifier.
292 #[inline]
293 pub const fn is_safe(self) -> bool {
294 matches!(self.safety, AlignmentSafety::Safe)
295 }
296
297 /// Returns the underlying position keyword, discarding the safety modifier.
298 #[inline]
299 pub const fn keyword(self) -> AlignContentKeyword {
300 self.keyword
301 }
302}
303
304#[cfg(feature = "parse")]
305impl FromCss for AlignContent {
306 fn from_css<'i>(input: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
307 let first = input.expect_ident()?.clone();
308 cssparser::match_ignore_ascii_case! { &*first,
309 "safe" => {
310 let pos = input.expect_ident()?.clone();
311 cssparser::match_ignore_ascii_case! { &*pos,
312 "start" => Ok(Self::SAFE_START),
313 "end" => Ok(Self::SAFE_END),
314 "flex-start" => Ok(Self::SAFE_FLEX_START),
315 "flex-end" => Ok(Self::SAFE_FLEX_END),
316 "center" => Ok(Self::SAFE_CENTER),
317 _ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
318 }
319 },
320 "unsafe" => {
321 let pos = input.expect_ident()?.clone();
322 cssparser::match_ignore_ascii_case! { &*pos,
323 "start" => Ok(Self::START),
324 "end" => Ok(Self::END),
325 "flex-start" => Ok(Self::FLEX_START),
326 "flex-end" => Ok(Self::FLEX_END),
327 "center" => Ok(Self::CENTER),
328 _ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
329 }
330 },
331 "start" => Ok(Self::START),
332 "end" => Ok(Self::END),
333 "flex-start" => Ok(Self::FLEX_START),
334 "flex-end" => Ok(Self::FLEX_END),
335 "center" => Ok(Self::CENTER),
336 "stretch" => Ok(Self::STRETCH),
337 "space-between" => Ok(Self::SPACE_BETWEEN),
338 "space-evenly" => Ok(Self::SPACE_EVENLY),
339 "space-around" => Ok(Self::SPACE_AROUND),
340 _ => Err(input.new_unexpected_token_error(Token::Ident(first))),
341 }
342 }
343}
344
345#[cfg(feature = "parse")]
346crate::util::parse::from_str_from_css!(AlignContent);
347
348/// Sets the distribution of space between and around content items.
349/// For Flexbox it controls alignment in the main axis.
350/// For Grid it controls alignment in the inline axis.
351///
352/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content)
353pub type JustifyContent = AlignContent;
354
355// ---------------------------------------------------------------------------
356// Serde — custom impls preserve the pre-struct wire format (single tag string
357// per public spelling) so consumers reading data serialized before the refactor
358// continue to deserialize correctly.
359// ---------------------------------------------------------------------------
360
361/// Canonical tag-string set accepted by [`AlignItems`] serde deserialization, used in
362/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
363#[cfg(feature = "serde")]
364const ALIGN_ITEMS_NAMES: &[&str] = &[
365 "Start",
366 "End",
367 "FlexStart",
368 "FlexEnd",
369 "Center",
370 "Baseline",
371 "Stretch",
372 "SafeStart",
373 "SafeEnd",
374 "SafeFlexStart",
375 "SafeFlexEnd",
376 "SafeCenter",
377];
378
379#[cfg(feature = "serde")]
380impl serde::Serialize for AlignItems {
381 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
382 let name = match (self.keyword, self.safety) {
383 (AlignItemsKeyword::Start, AlignmentSafety::Unsafe) => "Start",
384 (AlignItemsKeyword::End, AlignmentSafety::Unsafe) => "End",
385 (AlignItemsKeyword::FlexStart, AlignmentSafety::Unsafe) => "FlexStart",
386 (AlignItemsKeyword::FlexEnd, AlignmentSafety::Unsafe) => "FlexEnd",
387 (AlignItemsKeyword::Center, AlignmentSafety::Unsafe) => "Center",
388 (AlignItemsKeyword::Baseline, _) => "Baseline",
389 (AlignItemsKeyword::Stretch, _) => "Stretch",
390 (AlignItemsKeyword::Start, AlignmentSafety::Safe) => "SafeStart",
391 (AlignItemsKeyword::End, AlignmentSafety::Safe) => "SafeEnd",
392 (AlignItemsKeyword::FlexStart, AlignmentSafety::Safe) => "SafeFlexStart",
393 (AlignItemsKeyword::FlexEnd, AlignmentSafety::Safe) => "SafeFlexEnd",
394 (AlignItemsKeyword::Center, AlignmentSafety::Safe) => "SafeCenter",
395 };
396 serializer.serialize_str(name)
397 }
398}
399
400#[cfg(feature = "serde")]
401impl<'de> serde::Deserialize<'de> for AlignItems {
402 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
403 struct AlignItemsVisitor;
404 impl<'de> serde::de::Visitor<'de> for AlignItemsVisitor {
405 type Value = AlignItems;
406 fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
407 fmt.write_str("an AlignItems variant tag string")
408 }
409 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
410 Ok(match v {
411 "Start" => AlignItems::START,
412 "End" => AlignItems::END,
413 "FlexStart" => AlignItems::FLEX_START,
414 "FlexEnd" => AlignItems::FLEX_END,
415 "Center" => AlignItems::CENTER,
416 "Baseline" => AlignItems::BASELINE,
417 "Stretch" => AlignItems::STRETCH,
418 "SafeStart" => AlignItems::SAFE_START,
419 "SafeEnd" => AlignItems::SAFE_END,
420 "SafeFlexStart" => AlignItems::SAFE_FLEX_START,
421 "SafeFlexEnd" => AlignItems::SAFE_FLEX_END,
422 "SafeCenter" => AlignItems::SAFE_CENTER,
423 other => return Err(E::unknown_variant(other, ALIGN_ITEMS_NAMES)),
424 })
425 }
426 }
427 deserializer.deserialize_str(AlignItemsVisitor)
428 }
429}
430
431/// Canonical tag-string set accepted by [`AlignContent`] serde deserialization, used in
432/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
433#[cfg(feature = "serde")]
434const ALIGN_CONTENT_NAMES: &[&str] = &[
435 "Start",
436 "End",
437 "FlexStart",
438 "FlexEnd",
439 "Center",
440 "Stretch",
441 "SpaceBetween",
442 "SpaceEvenly",
443 "SpaceAround",
444 "SafeStart",
445 "SafeEnd",
446 "SafeFlexStart",
447 "SafeFlexEnd",
448 "SafeCenter",
449];
450
451#[cfg(feature = "serde")]
452impl serde::Serialize for AlignContent {
453 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
454 let name = match (self.keyword, self.safety) {
455 (AlignContentKeyword::Start, AlignmentSafety::Unsafe) => "Start",
456 (AlignContentKeyword::End, AlignmentSafety::Unsafe) => "End",
457 (AlignContentKeyword::FlexStart, AlignmentSafety::Unsafe) => "FlexStart",
458 (AlignContentKeyword::FlexEnd, AlignmentSafety::Unsafe) => "FlexEnd",
459 (AlignContentKeyword::Center, AlignmentSafety::Unsafe) => "Center",
460 (AlignContentKeyword::Stretch, _) => "Stretch",
461 (AlignContentKeyword::SpaceBetween, _) => "SpaceBetween",
462 (AlignContentKeyword::SpaceEvenly, _) => "SpaceEvenly",
463 (AlignContentKeyword::SpaceAround, _) => "SpaceAround",
464 (AlignContentKeyword::Start, AlignmentSafety::Safe) => "SafeStart",
465 (AlignContentKeyword::End, AlignmentSafety::Safe) => "SafeEnd",
466 (AlignContentKeyword::FlexStart, AlignmentSafety::Safe) => "SafeFlexStart",
467 (AlignContentKeyword::FlexEnd, AlignmentSafety::Safe) => "SafeFlexEnd",
468 (AlignContentKeyword::Center, AlignmentSafety::Safe) => "SafeCenter",
469 };
470 serializer.serialize_str(name)
471 }
472}
473
474#[cfg(feature = "serde")]
475impl<'de> serde::Deserialize<'de> for AlignContent {
476 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
477 struct AlignContentVisitor;
478 impl<'de> serde::de::Visitor<'de> for AlignContentVisitor {
479 type Value = AlignContent;
480 fn expecting(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
481 fmt.write_str("an AlignContent variant tag string")
482 }
483 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
484 Ok(match v {
485 "Start" => AlignContent::START,
486 "End" => AlignContent::END,
487 "FlexStart" => AlignContent::FLEX_START,
488 "FlexEnd" => AlignContent::FLEX_END,
489 "Center" => AlignContent::CENTER,
490 "Stretch" => AlignContent::STRETCH,
491 "SpaceBetween" => AlignContent::SPACE_BETWEEN,
492 "SpaceEvenly" => AlignContent::SPACE_EVENLY,
493 "SpaceAround" => AlignContent::SPACE_AROUND,
494 "SafeStart" => AlignContent::SAFE_START,
495 "SafeEnd" => AlignContent::SAFE_END,
496 "SafeFlexStart" => AlignContent::SAFE_FLEX_START,
497 "SafeFlexEnd" => AlignContent::SAFE_FLEX_END,
498 "SafeCenter" => AlignContent::SAFE_CENTER,
499 other => return Err(E::unknown_variant(other, ALIGN_CONTENT_NAMES)),
500 })
501 }
502 }
503 deserializer.deserialize_str(AlignContentVisitor)
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use core::mem::size_of;
511
512 // Size budget — struct = 1B keyword + 1B safety, no niche packing.
513 // Pre-refactor each was a single-byte enum; spec §V13 caps regression at +2B.
514 #[test]
515 fn align_types_within_size_budget() {
516 assert!(size_of::<AlignItems>() <= 2, "AlignItems grew to {}", size_of::<AlignItems>());
517 assert!(size_of::<AlignContent>() <= 2, "AlignContent grew to {}", size_of::<AlignContent>());
518 assert!(size_of::<Option<AlignItems>>() <= 3);
519 assert!(size_of::<Option<AlignContent>>() <= 3);
520 }
521
522 #[test]
523 fn align_items_is_safe() {
524 assert!(AlignItems::SAFE_START.is_safe());
525 assert!(AlignItems::SAFE_END.is_safe());
526 assert!(AlignItems::SAFE_FLEX_START.is_safe());
527 assert!(AlignItems::SAFE_FLEX_END.is_safe());
528 assert!(AlignItems::SAFE_CENTER.is_safe());
529 assert!(!AlignItems::START.is_safe());
530 assert!(!AlignItems::END.is_safe());
531 assert!(!AlignItems::FLEX_START.is_safe());
532 assert!(!AlignItems::FLEX_END.is_safe());
533 assert!(!AlignItems::CENTER.is_safe());
534 assert!(!AlignItems::BASELINE.is_safe());
535 assert!(!AlignItems::STRETCH.is_safe());
536 }
537
538 #[test]
539 fn align_items_keyword_strips_safe() {
540 assert_eq!(AlignItems::SAFE_START.keyword(), AlignItemsKeyword::Start);
541 assert_eq!(AlignItems::SAFE_END.keyword(), AlignItemsKeyword::End);
542 assert_eq!(AlignItems::SAFE_FLEX_START.keyword(), AlignItemsKeyword::FlexStart);
543 assert_eq!(AlignItems::SAFE_FLEX_END.keyword(), AlignItemsKeyword::FlexEnd);
544 assert_eq!(AlignItems::SAFE_CENTER.keyword(), AlignItemsKeyword::Center);
545 }
546
547 #[test]
548 fn align_items_keyword_passthrough() {
549 assert_eq!(AlignItems::START.keyword(), AlignItemsKeyword::Start);
550 assert_eq!(AlignItems::STRETCH.keyword(), AlignItemsKeyword::Stretch);
551 assert_eq!(AlignItems::BASELINE.keyword(), AlignItemsKeyword::Baseline);
552 assert_eq!(AlignItems::FLEX_START.keyword(), AlignItemsKeyword::FlexStart);
553 }
554
555 #[test]
556 fn align_content_is_safe() {
557 assert!(AlignContent::SAFE_START.is_safe());
558 assert!(AlignContent::SAFE_CENTER.is_safe());
559 assert!(!AlignContent::SPACE_BETWEEN.is_safe());
560 assert!(!AlignContent::STRETCH.is_safe());
561 }
562
563 #[test]
564 fn align_content_keyword_strips_safe() {
565 assert_eq!(AlignContent::SAFE_START.keyword(), AlignContentKeyword::Start);
566 assert_eq!(AlignContent::SAFE_FLEX_END.keyword(), AlignContentKeyword::FlexEnd);
567 assert_eq!(AlignContent::SAFE_CENTER.keyword(), AlignContentKeyword::Center);
568 assert_eq!(AlignContent::SPACE_BETWEEN.keyword(), AlignContentKeyword::SpaceBetween);
569 }
570
571 #[test]
572 fn align_content_keyword_reversed_swaps_start_end() {
573 assert_eq!(AlignContentKeyword::Start.reversed(), AlignContentKeyword::End);
574 assert_eq!(AlignContentKeyword::End.reversed(), AlignContentKeyword::Start);
575 assert_eq!(AlignContentKeyword::FlexStart.reversed(), AlignContentKeyword::FlexEnd);
576 assert_eq!(AlignContentKeyword::FlexEnd.reversed(), AlignContentKeyword::FlexStart);
577 // Stretch reverses to End — preserves pre-refactor behaviour.
578 assert_eq!(AlignContentKeyword::Stretch.reversed(), AlignContentKeyword::End);
579 assert_eq!(AlignContentKeyword::Center.reversed(), AlignContentKeyword::Center);
580 assert_eq!(AlignContentKeyword::SpaceBetween.reversed(), AlignContentKeyword::SpaceBetween);
581 assert_eq!(AlignContentKeyword::SpaceEvenly.reversed(), AlignContentKeyword::SpaceEvenly);
582 assert_eq!(AlignContentKeyword::SpaceAround.reversed(), AlignContentKeyword::SpaceAround);
583 }
584
585 #[cfg(feature = "parse")]
586 #[test]
587 fn parse_align_items_plain() {
588 assert_eq!("start".parse::<AlignItems>().unwrap(), AlignItems::START);
589 assert_eq!("end".parse::<AlignItems>().unwrap(), AlignItems::END);
590 assert_eq!("flex-start".parse::<AlignItems>().unwrap(), AlignItems::FLEX_START);
591 assert_eq!("flex-end".parse::<AlignItems>().unwrap(), AlignItems::FLEX_END);
592 assert_eq!("center".parse::<AlignItems>().unwrap(), AlignItems::CENTER);
593 assert_eq!("baseline".parse::<AlignItems>().unwrap(), AlignItems::BASELINE);
594 assert_eq!("stretch".parse::<AlignItems>().unwrap(), AlignItems::STRETCH);
595 }
596
597 #[cfg(feature = "parse")]
598 #[test]
599 fn parse_align_items_safe() {
600 assert_eq!("safe start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_START);
601 assert_eq!("safe end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_END);
602 assert_eq!("safe flex-start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_START);
603 assert_eq!("safe flex-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_END);
604 assert_eq!("safe center".parse::<AlignItems>().unwrap(), AlignItems::SAFE_CENTER);
605 }
606
607 #[cfg(feature = "parse")]
608 #[test]
609 fn parse_align_items_safe_case_insensitive() {
610 assert_eq!("SAFE Start".parse::<AlignItems>().unwrap(), AlignItems::SAFE_START);
611 assert_eq!("Safe FLEX-end".parse::<AlignItems>().unwrap(), AlignItems::SAFE_FLEX_END);
612 }
613
614 #[cfg(feature = "parse")]
615 #[test]
616 fn parse_align_items_unsafe_drops_modifier() {
617 assert_eq!("unsafe start".parse::<AlignItems>().unwrap(), AlignItems::START);
618 assert_eq!("unsafe end".parse::<AlignItems>().unwrap(), AlignItems::END);
619 assert_eq!("unsafe center".parse::<AlignItems>().unwrap(), AlignItems::CENTER);
620 }
621
622 #[cfg(feature = "parse")]
623 #[test]
624 fn parse_align_items_rejects_invalid_safe_combos() {
625 assert!("safe stretch".parse::<AlignItems>().is_err());
626 assert!("safe baseline".parse::<AlignItems>().is_err());
627 assert!("safe space-between".parse::<AlignItems>().is_err());
628 assert!("safe".parse::<AlignItems>().is_err());
629 assert!("safe garbage".parse::<AlignItems>().is_err());
630 assert!("unsafe stretch".parse::<AlignItems>().is_err());
631 assert!("unsafe baseline".parse::<AlignItems>().is_err());
632 }
633
634 #[cfg(feature = "parse")]
635 #[test]
636 fn parse_align_content_plain() {
637 assert_eq!("start".parse::<AlignContent>().unwrap(), AlignContent::START);
638 assert_eq!("space-between".parse::<AlignContent>().unwrap(), AlignContent::SPACE_BETWEEN);
639 assert_eq!("space-evenly".parse::<AlignContent>().unwrap(), AlignContent::SPACE_EVENLY);
640 assert_eq!("space-around".parse::<AlignContent>().unwrap(), AlignContent::SPACE_AROUND);
641 assert_eq!("stretch".parse::<AlignContent>().unwrap(), AlignContent::STRETCH);
642 }
643
644 #[cfg(feature = "parse")]
645 #[test]
646 fn parse_align_content_safe() {
647 assert_eq!("safe start".parse::<AlignContent>().unwrap(), AlignContent::SAFE_START);
648 assert_eq!("safe end".parse::<AlignContent>().unwrap(), AlignContent::SAFE_END);
649 assert_eq!("safe flex-start".parse::<AlignContent>().unwrap(), AlignContent::SAFE_FLEX_START);
650 assert_eq!("safe flex-end".parse::<AlignContent>().unwrap(), AlignContent::SAFE_FLEX_END);
651 assert_eq!("safe center".parse::<AlignContent>().unwrap(), AlignContent::SAFE_CENTER);
652 }
653
654 #[cfg(feature = "parse")]
655 #[test]
656 fn parse_align_content_unsafe_drops_modifier() {
657 assert_eq!("unsafe start".parse::<AlignContent>().unwrap(), AlignContent::START);
658 assert_eq!("unsafe flex-end".parse::<AlignContent>().unwrap(), AlignContent::FLEX_END);
659 }
660
661 #[cfg(feature = "parse")]
662 #[test]
663 fn parse_align_content_rejects_invalid_safe_combos() {
664 assert!("safe stretch".parse::<AlignContent>().is_err());
665 assert!("safe space-between".parse::<AlignContent>().is_err());
666 assert!("safe space-evenly".parse::<AlignContent>().is_err());
667 assert!("safe space-around".parse::<AlignContent>().is_err());
668 assert!("safe".parse::<AlignContent>().is_err());
669 assert!("unsafe stretch".parse::<AlignContent>().is_err());
670 assert!("unsafe space-between".parse::<AlignContent>().is_err());
671 }
672
673 #[cfg(feature = "serde")]
674 #[test]
675 fn serde_align_items_round_trip() {
676 let cases = [
677 (AlignItems::START, "\"Start\""),
678 (AlignItems::END, "\"End\""),
679 (AlignItems::FLEX_START, "\"FlexStart\""),
680 (AlignItems::FLEX_END, "\"FlexEnd\""),
681 (AlignItems::CENTER, "\"Center\""),
682 (AlignItems::BASELINE, "\"Baseline\""),
683 (AlignItems::STRETCH, "\"Stretch\""),
684 (AlignItems::SAFE_START, "\"SafeStart\""),
685 (AlignItems::SAFE_END, "\"SafeEnd\""),
686 (AlignItems::SAFE_FLEX_START, "\"SafeFlexStart\""),
687 (AlignItems::SAFE_FLEX_END, "\"SafeFlexEnd\""),
688 (AlignItems::SAFE_CENTER, "\"SafeCenter\""),
689 ];
690 for (value, expected) in cases {
691 let serialized = serde_json::to_string(&value).unwrap();
692 assert_eq!(serialized, expected, "serialize {:?}", value);
693 let deserialized: AlignItems = serde_json::from_str(expected).unwrap();
694 assert_eq!(deserialized, value, "round-trip {:?}", value);
695 }
696 assert!(serde_json::from_str::<AlignItems>("\"NotAVariant\"").is_err());
697 }
698
699 #[cfg(feature = "serde")]
700 #[test]
701 fn serde_align_content_round_trip() {
702 let cases = [
703 (AlignContent::START, "\"Start\""),
704 (AlignContent::END, "\"End\""),
705 (AlignContent::FLEX_START, "\"FlexStart\""),
706 (AlignContent::FLEX_END, "\"FlexEnd\""),
707 (AlignContent::CENTER, "\"Center\""),
708 (AlignContent::STRETCH, "\"Stretch\""),
709 (AlignContent::SPACE_BETWEEN, "\"SpaceBetween\""),
710 (AlignContent::SPACE_EVENLY, "\"SpaceEvenly\""),
711 (AlignContent::SPACE_AROUND, "\"SpaceAround\""),
712 (AlignContent::SAFE_START, "\"SafeStart\""),
713 (AlignContent::SAFE_END, "\"SafeEnd\""),
714 (AlignContent::SAFE_FLEX_START, "\"SafeFlexStart\""),
715 (AlignContent::SAFE_FLEX_END, "\"SafeFlexEnd\""),
716 (AlignContent::SAFE_CENTER, "\"SafeCenter\""),
717 ];
718 for (value, expected) in cases {
719 let serialized = serde_json::to_string(&value).unwrap();
720 assert_eq!(serialized, expected, "serialize {:?}", value);
721 let deserialized: AlignContent = serde_json::from_str(expected).unwrap();
722 assert_eq!(deserialized, value, "round-trip {:?}", value);
723 }
724 assert!(serde_json::from_str::<AlignContent>("\"NotAVariant\"").is_err());
725 }
726}