Skip to main content

taffy/compute/common/
alignment.rs

1//! Generic CSS alignment code that is shared between both the Flexbox and CSS Grid algorithms.
2use crate::style::{AlignContent, AlignContentKeyword, AlignmentSafety};
3
4/// Resolve any spec-defined fallbacks for the given [`AlignContent`] value, returning the
5/// bare position keyword the alignment math should use.
6///
7/// In addition to the spec at <https://www.w3.org/TR/css-align-3/> this implementation follows
8/// the resolution of <https://github.com/w3c/csswg-drafts/issues/10154>.
9pub(crate) fn apply_alignment_fallback(
10    free_space: f32,
11    num_items: usize,
12    alignment_mode: AlignContent,
13) -> AlignContentKeyword {
14    let mut keyword = alignment_mode.keyword;
15    let mut is_safe = matches!(alignment_mode.safety, AlignmentSafety::Safe);
16
17    // 1. If there is only a single item being aligned or the items overflow the container, the
18    //    distributed alignment keywords (`stretch`, `space-*`) fall back to a positional keyword
19    //    and gain implicit `safe` semantics so step 2 can flip them to `Start` on overflow.
20    //    https://www.w3.org/TR/css-align-3/#distribution-values
21    if num_items <= 1 || free_space <= 0.0 {
22        (keyword, is_safe) = match keyword {
23            AlignContentKeyword::Stretch | AlignContentKeyword::SpaceBetween => (AlignContentKeyword::FlexStart, true),
24            AlignContentKeyword::SpaceAround | AlignContentKeyword::SpaceEvenly => (AlignContentKeyword::Center, true),
25            other => (other, is_safe),
26        };
27    }
28
29    // 2. Safe alignment falls back to `Start` whenever the alignment subject would overflow the
30    //    alignment container.
31    if free_space <= 0.0 && is_safe {
32        keyword = AlignContentKeyword::Start;
33    }
34
35    keyword
36}
37
38/// Generic alignment function that is used:
39///   - For both align-content and justify-content alignment
40///   - For both the Flexbox and CSS Grid algorithms
41///
42/// CSS Grid does not apply gaps as part of alignment, so the gap parameter should
43/// always be set to zero for CSS Grid.
44pub(crate) fn compute_alignment_offset(
45    free_space: f32,
46    num_items: usize,
47    gap: f32,
48    alignment_mode: AlignContentKeyword,
49    layout_is_flex_reversed: bool,
50    is_first: bool,
51) -> f32 {
52    if is_first {
53        match alignment_mode {
54            AlignContentKeyword::Start => 0.0,
55            AlignContentKeyword::FlexStart => {
56                if layout_is_flex_reversed {
57                    free_space
58                } else {
59                    0.0
60                }
61            }
62            AlignContentKeyword::End => free_space,
63            AlignContentKeyword::FlexEnd => {
64                if layout_is_flex_reversed {
65                    0.0
66                } else {
67                    free_space
68                }
69            }
70            AlignContentKeyword::Center => free_space / 2.0,
71            AlignContentKeyword::Stretch => 0.0,
72            AlignContentKeyword::SpaceBetween => 0.0,
73            AlignContentKeyword::SpaceAround => {
74                if free_space >= 0.0 {
75                    (free_space / num_items as f32) / 2.0
76                } else {
77                    free_space / 2.0
78                }
79            }
80            AlignContentKeyword::SpaceEvenly => {
81                if free_space >= 0.0 {
82                    free_space / (num_items + 1) as f32
83                } else {
84                    free_space / 2.0
85                }
86            }
87        }
88    } else {
89        let free_space = free_space.max(0.0);
90        gap + match alignment_mode {
91            AlignContentKeyword::Start
92            | AlignContentKeyword::FlexStart
93            | AlignContentKeyword::End
94            | AlignContentKeyword::FlexEnd
95            | AlignContentKeyword::Center
96            | AlignContentKeyword::Stretch => 0.0,
97            AlignContentKeyword::SpaceBetween => free_space / (num_items - 1) as f32,
98            AlignContentKeyword::SpaceAround => free_space / num_items as f32,
99            AlignContentKeyword::SpaceEvenly => free_space / (num_items + 1) as f32,
100        }
101    }
102}