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, Overflow};
3
4/// The first baseline of a child box (measured from the top of its border box, like [`crate::Baselines`]) as used
5/// for baseline alignment and for determining its container's baseline, given the baseline it reported, its
6/// border-box height, and its vertical `overflow`.
7///
8/// Scroll containers' baselines are determined from their content as if scrolled to the initial position, but are
9/// additionally clamped to their border box. A scroll container with no baseline synthesizes one from its border-box
10/// bottom edge. See <https://github.com/w3c/csswg-drafts/issues/7660>
11#[inline]
12pub(crate) fn child_first_baseline(first_baseline: Option<f32>, height: f32, overflow_y: Overflow) -> Option<f32> {
13    if overflow_y.is_scroll_container() {
14        Some(first_baseline.unwrap_or(height).min(height).max(0.0))
15    } else {
16        first_baseline
17    }
18}
19
20/// Resolve the `safe`/`unsafe` overflow-position fallback for a self-level alignment value
21/// (used by `align-self` / `justify-self`-style sites and by absolutely-positioned items in
22/// flex/grid). If the alignment subject overflows its alignment container and the requested
23/// alignment is `safe`, fall back to logical `Start` per CSS Box Alignment
24/// <https://www.w3.org/TR/css-align-3/#overflow-values>. Otherwise drop the safety modifier
25/// and return the bare keyword.
26#[inline]
27pub(crate) fn resolve_self_alignment_safety(alignment: AlignItems, overflows: bool) -> AlignItemsKeyword {
28    if matches!(alignment.safety, AlignmentSafety::Safe) && overflows {
29        AlignItemsKeyword::Start
30    } else {
31        alignment.keyword
32    }
33}
34
35/// Resolve any spec-defined fallbacks for the given [`AlignContent`] value, returning the
36/// bare position keyword the alignment math should use.
37///
38/// In addition to the spec at <https://www.w3.org/TR/css-align-3/> this implementation follows
39/// the resolution of <https://github.com/w3c/csswg-drafts/issues/10154>.
40///
41/// `normal` behaves differently in each layout mode, so callers must resolve
42/// [`AlignContentKeyword::Normal`] to the keyword it behaves as before calling this function
43/// (if it is not resolved then it is treated as `stretch`).
44pub(crate) fn apply_alignment_fallback(
45    free_space: f32,
46    num_items: usize,
47    alignment_mode: AlignContent,
48) -> AlignContentKeyword {
49    debug_assert!(
50        alignment_mode.keyword != AlignContentKeyword::Normal,
51        "normal must be resolved before computing content alignment"
52    );
53    let mut keyword = match alignment_mode.keyword {
54        AlignContentKeyword::Normal => AlignContentKeyword::Stretch,
55        keyword => keyword,
56    };
57    let mut is_safe = matches!(alignment_mode.safety, AlignmentSafety::Safe);
58
59    // 1. If there is only a single item being aligned or the items overflow the container, the
60    //    distributed alignment keywords (`stretch`, `space-*`) fall back to a positional keyword
61    //    and gain implicit `safe` semantics so step 2 can flip them to `Start` on overflow.
62    //    https://www.w3.org/TR/css-align-3/#distribution-values
63    if num_items <= 1 || free_space <= 0.0 {
64        (keyword, is_safe) = match keyword {
65            AlignContentKeyword::Stretch | AlignContentKeyword::SpaceBetween => (AlignContentKeyword::FlexStart, true),
66            AlignContentKeyword::SpaceAround | AlignContentKeyword::SpaceEvenly => (AlignContentKeyword::Center, true),
67            other => (other, is_safe),
68        };
69    }
70
71    // 2. Safe alignment falls back to `Start` whenever the alignment subject would overflow the
72    //    alignment container.
73    if free_space <= 0.0 && is_safe {
74        keyword = AlignContentKeyword::Start;
75    }
76
77    keyword
78}
79
80/// Generic alignment function that is used:
81///   - For both align-content and justify-content alignment
82///   - For both the Flexbox and CSS Grid algorithms
83///
84/// CSS Grid does not apply gaps as part of alignment, so the gap parameter should
85/// always be set to zero for CSS Grid.
86pub(crate) fn compute_alignment_offset(
87    free_space: f32,
88    num_items: usize,
89    gap: f32,
90    alignment_mode: AlignContentKeyword,
91    layout_is_flex_reversed: bool,
92    is_first: bool,
93) -> f32 {
94    if is_first {
95        match alignment_mode {
96            AlignContentKeyword::Start => 0.0,
97            AlignContentKeyword::FlexStart => {
98                if layout_is_flex_reversed {
99                    free_space
100                } else {
101                    0.0
102                }
103            }
104            AlignContentKeyword::End => free_space,
105            AlignContentKeyword::FlexEnd => {
106                if layout_is_flex_reversed {
107                    0.0
108                } else {
109                    free_space
110                }
111            }
112            AlignContentKeyword::Center => free_space / 2.0,
113            AlignContentKeyword::Normal | AlignContentKeyword::Stretch => 0.0,
114            AlignContentKeyword::SpaceBetween => 0.0,
115            AlignContentKeyword::SpaceAround => {
116                if free_space >= 0.0 {
117                    (free_space / num_items as f32) / 2.0
118                } else {
119                    free_space / 2.0
120                }
121            }
122            AlignContentKeyword::SpaceEvenly => {
123                if free_space >= 0.0 {
124                    free_space / (num_items + 1) as f32
125                } else {
126                    free_space / 2.0
127                }
128            }
129        }
130    } else {
131        let free_space = free_space.max(0.0);
132        gap + match alignment_mode {
133            AlignContentKeyword::Start
134            | AlignContentKeyword::FlexStart
135            | AlignContentKeyword::End
136            | AlignContentKeyword::FlexEnd
137            | AlignContentKeyword::Center
138            | AlignContentKeyword::Normal
139            | AlignContentKeyword::Stretch => 0.0,
140            AlignContentKeyword::SpaceBetween => free_space / (num_items - 1) as f32,
141            AlignContentKeyword::SpaceAround => free_space / num_items as f32,
142            AlignContentKeyword::SpaceEvenly => free_space / (num_items + 1) as f32,
143        }
144    }
145}