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