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}