• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Implements the track sizing algorithm
2 //! <https://www.w3.org/TR/css-grid-1/#layout-algorithm>
3 use super::types::{GridItem, GridTrack, TrackCounts};
4 use crate::geometry::{AbstractAxis, Line, Size};
5 use crate::style::{
6     AlignContent, AlignSelf, AvailableSpace, LengthPercentage, MaxTrackSizingFunction, MinTrackSizingFunction,
7 };
8 use crate::style_helpers::TaffyMinContent;
9 use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, SizingMode};
10 use crate::util::sys::{f32_max, f32_min, Vec};
11 use crate::util::{MaybeMath, ResolveOrZero};
12 use core::cmp::Ordering;
13 
14 /// Takes an axis, and a list of grid items sorted firstly by whether they cross a flex track
15 /// in the specified axis (items that don't cross a flex track first) and then by the number
16 /// of tracks they cross in specified axis (ascending order).
17 struct ItemBatcher {
18     /// The axis in which the ItemBatcher is operating. Used when querying properties from items.
19     axis: AbstractAxis,
20     /// The starting index of the current batch
21     index_offset: usize,
22     /// The span of the items in the current batch
23     current_span: u16,
24     /// Whether the current batch of items cross a flexible track
25     current_is_flex: bool,
26 }
27 
28 impl ItemBatcher {
29     /// Create a new ItemBatcher for the specified axis
30     #[inline(always)]
new(axis: AbstractAxis) -> Self31     fn new(axis: AbstractAxis) -> Self {
32         ItemBatcher { index_offset: 0, axis, current_span: 1, current_is_flex: false }
33     }
34 
35     /// This is basically a manual version of Iterator::next which passes `items`
36     /// in as a parameter on each iteration to work around borrow checker rules
37     #[inline]
next<'items>(&mut self, items: &'items mut [GridItem]) -> Option<(&'items mut [GridItem], bool)>38     fn next<'items>(&mut self, items: &'items mut [GridItem]) -> Option<(&'items mut [GridItem], bool)> {
39         if self.current_is_flex || self.index_offset >= items.len() {
40             return None;
41         }
42 
43         let item = &items[self.index_offset];
44         self.current_span = item.span(self.axis);
45         self.current_is_flex = item.crosses_flexible_track(self.axis);
46 
47         let next_index_offset = if self.current_is_flex {
48             items.len()
49         } else {
50             items
51                 .iter()
52                 .position(|item: &GridItem| {
53                     item.crosses_flexible_track(self.axis) || item.span(self.axis) > self.current_span
54                 })
55                 .unwrap_or(items.len())
56         };
57 
58         let batch_range = self.index_offset..next_index_offset;
59         self.index_offset = next_index_offset;
60 
61         let batch = &mut items[batch_range];
62         Some((batch, self.current_is_flex))
63     }
64 }
65 
66 /// This struct captures a bunch of variables which are used to compute the intrinsic sizes of children so that those variables
67 /// don't have to be passed around all over the place below. It then has methods that implement the intrinsic sizing computations
68 struct IntrisicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
69 where
70     Tree: LayoutPartialTree,
71     EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
72 {
73     /// The layout tree
74     tree: &'tree mut Tree,
75     /// The tracks in the opposite axis to the one we are currently sizing
76     other_axis_tracks: &'oat [GridTrack],
77     /// A function that computes an estimate of an other-axis track's size which is passed to
78     /// the child size measurement functions
79     get_track_size_estimate: EstimateFunction,
80     /// The axis we are currently sizing
81     axis: AbstractAxis,
82     /// The available grid space
83     inner_node_size: Size<Option<f32>>,
84 }
85 
86 impl<Tree, EstimateFunction> IntrisicSizeMeasurer<'_, '_, Tree, EstimateFunction>
87 where
88     Tree: LayoutPartialTree,
89     EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
90 {
91     /// Compute the available_space to be passed to the child sizing functions
92     /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
93     /// axis to the one currently being sized.
94     /// https://www.w3.org/TR/css-grid-1/#algo-overview
95     #[inline(always)]
available_space(&self, item: &mut GridItem) -> Size<Option<f32>>96     fn available_space(&self, item: &mut GridItem) -> Size<Option<f32>> {
97         item.available_space_cached(
98             self.axis,
99             self.other_axis_tracks,
100             self.inner_node_size.get(self.axis.other()),
101             &self.get_track_size_estimate,
102         )
103     }
104 
105     /// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
106     /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
107     #[inline(always)]
margins_axis_sums_with_baseline_shims(&self, item: &GridItem) -> Size<f32>108     fn margins_axis_sums_with_baseline_shims(&self, item: &GridItem) -> Size<f32> {
109         item.margins_axis_sums_with_baseline_shims(self.inner_node_size.width)
110     }
111 
112     /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
113     #[inline(always)]
min_content_contribution(&mut self, item: &mut GridItem) -> f32114     fn min_content_contribution(&mut self, item: &mut GridItem) -> f32 {
115         let available_space = self.available_space(item);
116         let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
117         let contribution =
118             item.min_content_contribution_cached(self.axis, self.tree, available_space, self.inner_node_size);
119         contribution + margin_axis_sums.get(self.axis)
120     }
121 
122     /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
123     #[inline(always)]
max_content_contribution(&mut self, item: &mut GridItem) -> f32124     fn max_content_contribution(&mut self, item: &mut GridItem) -> f32 {
125         let available_space = self.available_space(item);
126         let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
127         let contribution =
128             item.max_content_contribution_cached(self.axis, self.tree, available_space, self.inner_node_size);
129         contribution + margin_axis_sums.get(self.axis)
130     }
131 
132     /// The minimum contribution of an item is the smallest outer size it can have.
133     /// Specifically:
134     ///   - If the item’s computed preferred size behaves as auto or depends on the size of its containing block in the relevant axis:
135     ///     Its minimum contribution is the outer size that would result from assuming the item’s used minimum size as its preferred size;
136     ///   - Else the item’s minimum contribution is its min-content contribution.
137     ///
138     /// Because the minimum contribution often depends on the size of the item’s content, it is considered a type of intrinsic size contribution.
139     #[inline(always)]
minimum_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32140     fn minimum_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
141         let available_space = self.available_space(item);
142         let margin_axis_sums = self.margins_axis_sums_with_baseline_shims(item);
143         let contribution =
144             item.minimum_contribution_cached(self.tree, self.axis, axis_tracks, available_space, self.inner_node_size);
145         contribution + margin_axis_sums.get(self.axis)
146     }
147 }
148 
149 /// To make track sizing efficient we want to order tracks
150 /// Here a placement is either a Line<i16> representing a row-start/row-end or a column-start/column-end
151 #[inline(always)]
cmp_by_cross_flex_then_span_then_start( axis: AbstractAxis, ) -> impl FnMut(&GridItem, &GridItem) -> Ordering152 pub(super) fn cmp_by_cross_flex_then_span_then_start(
153     axis: AbstractAxis,
154 ) -> impl FnMut(&GridItem, &GridItem) -> Ordering {
155     move |item_a: &GridItem, item_b: &GridItem| -> Ordering {
156         match (item_a.crosses_flexible_track(axis), item_b.crosses_flexible_track(axis)) {
157             (false, true) => Ordering::Less,
158             (true, false) => Ordering::Greater,
159             _ => {
160                 let placement_a = item_a.placement(axis);
161                 let placement_b = item_b.placement(axis);
162                 match placement_a.span().cmp(&placement_b.span()) {
163                     Ordering::Less => Ordering::Less,
164                     Ordering::Greater => Ordering::Greater,
165                     Ordering::Equal => placement_a.start.cmp(&placement_b.start),
166                 }
167             }
168         }
169     }
170 }
171 
172 /// When applying the track sizing algorithm and estimating the size in the other axis for content sizing items
173 /// we should take into account align-content/justify-content if both the grid container and all items in the
174 /// other axis have definite sizes. This function computes such a per-gutter additional size adjustment.
175 #[inline(always)]
compute_alignment_gutter_adjustment( alignment: AlignContent, axis_inner_node_size: Option<f32>, get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>, tracks: &[GridTrack], ) -> f32176 pub(super) fn compute_alignment_gutter_adjustment(
177     alignment: AlignContent,
178     axis_inner_node_size: Option<f32>,
179     get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
180     tracks: &[GridTrack],
181 ) -> f32 {
182     if tracks.len() <= 1 {
183         return 0.0;
184     }
185 
186     // As items never cross the outermost gutters in a grid, we can simplify our calculations by treating
187     // AlignContent::Start and AlignContent::End the same
188     let outer_gutter_weight = match alignment {
189         AlignContent::Start => 1,
190         AlignContent::FlexStart => 1,
191         AlignContent::End => 1,
192         AlignContent::FlexEnd => 1,
193         AlignContent::Center => 1,
194         AlignContent::Stretch => 0,
195         AlignContent::SpaceBetween => 0,
196         AlignContent::SpaceAround => 1,
197         AlignContent::SpaceEvenly => 1,
198     };
199 
200     let inner_gutter_weight = match alignment {
201         AlignContent::FlexStart => 0,
202         AlignContent::Start => 0,
203         AlignContent::FlexEnd => 0,
204         AlignContent::End => 0,
205         AlignContent::Center => 0,
206         AlignContent::Stretch => 0,
207         AlignContent::SpaceBetween => 1,
208         AlignContent::SpaceAround => 2,
209         AlignContent::SpaceEvenly => 1,
210     };
211 
212     if inner_gutter_weight == 0 {
213         return 0.0;
214     }
215 
216     if let Some(axis_inner_node_size) = axis_inner_node_size {
217         let free_space = tracks
218             .iter()
219             .map(|track| get_track_size_estimate(track, Some(axis_inner_node_size)))
220             .sum::<Option<f32>>()
221             .map(|track_size_sum| f32_max(0.0, axis_inner_node_size - track_size_sum))
222             .unwrap_or(0.0);
223 
224         let weighted_track_count =
225             (((tracks.len() - 3) / 2) * inner_gutter_weight as usize) + (2 * outer_gutter_weight as usize);
226 
227         return (free_space / weighted_track_count as f32) * inner_gutter_weight as f32;
228     }
229 
230     0.0
231 }
232 
233 /// Convert origin-zero coordinates track placement in grid track vector indexes
234 #[inline(always)]
resolve_item_track_indexes(items: &mut [GridItem], column_counts: TrackCounts, row_counts: TrackCounts)235 pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts: TrackCounts, row_counts: TrackCounts) {
236     for item in items {
237         item.column_indexes = item.column.map(|line| line.into_track_vec_index(column_counts) as u16);
238         item.row_indexes = item.row.map(|line| line.into_track_vec_index(row_counts) as u16);
239     }
240 }
241 
242 /// Determine (in each axis) whether the item crosses any flexible tracks
243 #[inline(always)]
determine_if_item_crosses_flexible_or_intrinsic_tracks( items: &mut Vec<GridItem>, columns: &[GridTrack], rows: &[GridTrack], )244 pub(super) fn determine_if_item_crosses_flexible_or_intrinsic_tracks(
245     items: &mut Vec<GridItem>,
246     columns: &[GridTrack],
247     rows: &[GridTrack],
248 ) {
249     for item in items {
250         item.crosses_flexible_column =
251             item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].is_flexible());
252         item.crosses_intrinsic_column =
253             item.track_range_excluding_lines(AbstractAxis::Inline).any(|i| columns[i].has_intrinsic_sizing_function());
254         item.crosses_flexible_row =
255             item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].is_flexible());
256         item.crosses_intrinsic_row =
257             item.track_range_excluding_lines(AbstractAxis::Block).any(|i| rows[i].has_intrinsic_sizing_function());
258     }
259 }
260 
261 /// Track sizing algorithm
262 /// Note: Gutters are treated as empty fixed-size tracks for the purpose of the track sizing algorithm.
263 #[allow(clippy::too_many_arguments)]
track_sizing_algorithm<Tree: LayoutPartialTree>( tree: &mut Tree, axis: AbstractAxis, axis_min_size: Option<f32>, axis_max_size: Option<f32>, axis_alignment: AlignContent, other_axis_alignment: AlignContent, available_grid_space: Size<AvailableSpace>, inner_node_size: Size<Option<f32>>, axis_tracks: &mut [GridTrack], other_axis_tracks: &mut [GridTrack], items: &mut [GridItem], get_track_size_estimate: fn(&GridTrack, Option<f32>) -> Option<f32>, has_baseline_aligned_item: bool, )264 pub(super) fn track_sizing_algorithm<Tree: LayoutPartialTree>(
265     tree: &mut Tree,
266     axis: AbstractAxis,
267     axis_min_size: Option<f32>,
268     axis_max_size: Option<f32>,
269     axis_alignment: AlignContent,
270     other_axis_alignment: AlignContent,
271     available_grid_space: Size<AvailableSpace>,
272     inner_node_size: Size<Option<f32>>,
273     axis_tracks: &mut [GridTrack],
274     other_axis_tracks: &mut [GridTrack],
275     items: &mut [GridItem],
276     get_track_size_estimate: fn(&GridTrack, Option<f32>) -> Option<f32>,
277     has_baseline_aligned_item: bool,
278 ) {
279     // 11.4 Initialise Track sizes
280     // Initialize each track’s base size and growth limit.
281     initialize_track_sizes(axis_tracks, inner_node_size.get(axis));
282 
283     // 11.5.1 Shim item baselines
284     if has_baseline_aligned_item {
285         resolve_item_baselines(tree, axis, items, inner_node_size);
286     }
287 
288     // If all tracks have base_size = growth_limit, then skip the rest of this function.
289     // Note: this can only happen both track sizing function have the same fixed track sizing function
290     if axis_tracks.iter().all(|track| track.base_size == track.growth_limit) {
291         return;
292     }
293 
294     // Pre-computations for 11.5 Resolve Intrinsic Track Sizes
295 
296     // Compute an additional amount to add to each spanned gutter when computing item's estimated size in the
297     // in the opposite axis based on the alignment, container size, and estimated track sizes in that axis
298     let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
299         other_axis_alignment,
300         inner_node_size.get(axis.other()),
301         get_track_size_estimate,
302         other_axis_tracks,
303     );
304     if other_axis_tracks.len() > 3 {
305         let len = other_axis_tracks.len();
306         let inner_gutter_tracks = other_axis_tracks[2..len].iter_mut().step_by(2);
307         for track in inner_gutter_tracks {
308             track.content_alignment_adjustment = gutter_alignment_adjustment;
309         }
310     }
311 
312     // 11.5 Resolve Intrinsic Track Sizes
313     resolve_intrinsic_track_sizes(
314         tree,
315         axis,
316         axis_tracks,
317         other_axis_tracks,
318         items,
319         available_grid_space.get(axis),
320         inner_node_size,
321         get_track_size_estimate,
322     );
323 
324     // 11.6. Maximise Tracks
325     // Distributes free space (if any) to tracks with FINITE growth limits, up to their limits.
326     maximise_tracks(axis_tracks, inner_node_size.get(axis), available_grid_space.get(axis));
327 
328     // For the purpose of the final two expansion steps ("Expand Flexible Tracks" and "Stretch auto Tracks"), we only want to expand
329     // into space generated by the grid container's size (as defined by either it's preferred size style or by it's parent node through
330     // something like stretch alignment), not just any available space. To do this we map definite available space to AvailableSpace::MaxContent
331     // in the case that inner_node_size is None
332     let axis_available_space_for_expansion = if let Some(available_space) = inner_node_size.get(axis) {
333         AvailableSpace::Definite(available_space)
334     } else {
335         match available_grid_space.get(axis) {
336             AvailableSpace::MinContent => AvailableSpace::MinContent,
337             AvailableSpace::MaxContent | AvailableSpace::Definite(_) => AvailableSpace::MaxContent,
338         }
339     };
340 
341     // 11.7. Expand Flexible Tracks
342     // This step sizes flexible tracks using the largest value it can assign to an fr without exceeding the available space.
343     expand_flexible_tracks(
344         tree,
345         axis,
346         axis_tracks,
347         items,
348         axis_min_size,
349         axis_max_size,
350         axis_available_space_for_expansion,
351         inner_node_size,
352     );
353 
354     // 11.8. Stretch auto Tracks
355     // This step expands tracks that have an auto max track sizing function by dividing any remaining positive, definite free space equally amongst them.
356     if axis_alignment == AlignContent::Stretch {
357         stretch_auto_tracks(axis_tracks, axis_min_size, axis_available_space_for_expansion);
358     }
359 }
360 
361 /// Whether it is a minimum or maximum size's space being distributed
362 /// This controls behaviour of the space distribution algorithm when distributing beyond limits
363 /// See "distributing space beyond limits" at https://www.w3.org/TR/css-grid-1/#extra-space
364 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
365 enum IntrinsicContributionType {
366     /// It's a minimum size's space being distributed
367     Minimum,
368     /// It's a maximum size's space being distributed
369     Maximum,
370 }
371 
372 /// Add any planned base size increases to the base size after a round of distributing space to base sizes
373 /// Reset the planed base size increase to zero ready for the next round.
374 #[inline(always)]
flush_planned_base_size_increases(tracks: &mut [GridTrack])375 fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
376     for track in tracks {
377         track.base_size += track.base_size_planned_increase;
378         track.base_size_planned_increase = 0.0;
379     }
380 }
381 
382 /// Add any planned growth limit increases to the growth limit after a round of distributing space to growth limits
383 /// Reset the planed growth limit increase to zero ready for the next round.
384 #[inline(always)]
flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool)385 fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool) {
386     for track in tracks {
387         if track.growth_limit_planned_increase > 0.0 {
388             track.growth_limit = if track.growth_limit == f32::INFINITY {
389                 track.base_size + track.growth_limit_planned_increase
390             } else {
391                 track.growth_limit + track.growth_limit_planned_increase
392             };
393             track.infinitely_growable = set_infinitely_growable;
394         } else {
395             track.infinitely_growable = false;
396         }
397         track.growth_limit_planned_increase = 0.0
398     }
399 }
400 
401 /// 11.4 Initialise Track sizes
402 /// Initialize each track’s base size and growth limit.
403 #[inline(always)]
initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: Option<f32>)404 fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: Option<f32>) {
405     for track in axis_tracks.iter_mut() {
406         // For each track, if the track’s min track sizing function is:
407         // - A fixed sizing function
408         //     Resolve to an absolute length and use that size as the track’s initial base size.
409         //     Note: Indefinite lengths cannot occur, as they’re treated as auto.
410         // - An intrinsic sizing function
411         //     Use an initial base size of zero.
412         track.base_size = track.min_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(0.0);
413 
414         // For each track, if the track’s max track sizing function is:
415         // - A fixed sizing function
416         //     Resolve to an absolute length and use that size as the track’s initial growth limit.
417         // - An intrinsic sizing function
418         //     Use an initial growth limit of infinity.
419         // - A flexible sizing function
420         //     Use an initial growth limit of infinity.
421         track.growth_limit =
422             track.max_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(f32::INFINITY);
423 
424         // In all cases, if the growth limit is less than the base size, increase the growth limit to match the base size.
425         if track.growth_limit < track.base_size {
426             track.growth_limit = track.base_size;
427         }
428     }
429 }
430 
431 /// 11.5.1 Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment.
resolve_item_baselines( tree: &mut impl LayoutPartialTree, axis: AbstractAxis, items: &mut [GridItem], inner_node_size: Size<Option<f32>>, )432 fn resolve_item_baselines(
433     tree: &mut impl LayoutPartialTree,
434     axis: AbstractAxis,
435     items: &mut [GridItem],
436     inner_node_size: Size<Option<f32>>,
437 ) {
438     // Sort items by track in the other axis (row) start position so that we can iterate items in groups which
439     // are in the same track in the other axis (row)
440     let other_axis = axis.other();
441     items.sort_by_key(|item| item.placement(other_axis).start);
442 
443     // Iterate over grid rows
444     let mut remaining_items = &mut items[0..];
445     while !remaining_items.is_empty() {
446         // Get the row index of the current row
447         let current_row = remaining_items[0].placement(other_axis).start;
448 
449         // Find the item index of the first item that is in a different row (or None if we've reached the end of the list)
450         let next_row_first_item =
451             remaining_items.iter().position(|item| item.placement(other_axis).start != current_row);
452 
453         // Use this index to split the `remaining_items` slice in two slices:
454         //    - A `row_items` slice containing the items (that start) in the current row
455         //    - A new `remaining_items` consisting of the remainder of the `remaining_items` slice
456         //      that hasn't been split off into `row_items
457         let row_items = if let Some(index) = next_row_first_item {
458             let (row_items, tail) = remaining_items.split_at_mut(index);
459             remaining_items = tail;
460             row_items
461         } else {
462             let row_items = remaining_items;
463             remaining_items = &mut [];
464             row_items
465         };
466 
467         // Count how many items in *this row* are baseline aligned
468         // If a row has one or zero items participating in baseline alignment then baseline alignment is a no-op
469         // for those items and we skip further computations for that row
470         let row_baseline_item_count = row_items.iter().filter(|item| item.align_self == AlignSelf::Baseline).count();
471         if row_baseline_item_count <= 1 {
472             continue;
473         }
474 
475         // Compute the baselines of all items in the row
476         for item in row_items.iter_mut() {
477             let measured_size_and_baselines = tree.perform_child_layout(
478                 item.node,
479                 Size::NONE,
480                 inner_node_size,
481                 Size::MIN_CONTENT,
482                 SizingMode::InherentSize,
483                 Line::FALSE,
484             );
485 
486             let baseline = measured_size_and_baselines.first_baselines.y;
487             let height = measured_size_and_baselines.size.height;
488 
489             item.baseline = Some(baseline.unwrap_or(height) + item.margin.top.resolve_or_zero(inner_node_size.width));
490         }
491 
492         // Compute the max baseline of all items in the row
493         let row_max_baseline =
494             row_items.iter().map(|item| item.baseline.unwrap_or(0.0)).max_by(|a, b| a.total_cmp(b)).unwrap();
495 
496         // Compute the baseline shim for each item in the row
497         for item in row_items.iter_mut() {
498             item.baseline_shim = row_max_baseline - item.baseline.unwrap_or(0.0);
499         }
500     }
501 }
502 
503 /// 11.5 Resolve Intrinsic Track Sizes
504 #[allow(clippy::too_many_arguments)]
resolve_intrinsic_track_sizes( tree: &mut impl LayoutPartialTree, axis: AbstractAxis, axis_tracks: &mut [GridTrack], other_axis_tracks: &[GridTrack], items: &mut [GridItem], axis_available_grid_space: AvailableSpace, inner_node_size: Size<Option<f32>>, get_track_size_estimate: fn(&GridTrack, Option<f32>) -> Option<f32>, )505 fn resolve_intrinsic_track_sizes(
506     tree: &mut impl LayoutPartialTree,
507     axis: AbstractAxis,
508     axis_tracks: &mut [GridTrack],
509     other_axis_tracks: &[GridTrack],
510     items: &mut [GridItem],
511     axis_available_grid_space: AvailableSpace,
512     inner_node_size: Size<Option<f32>>,
513     get_track_size_estimate: fn(&GridTrack, Option<f32>) -> Option<f32>,
514 ) {
515     // Step 1. Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment.
516 
517     // Already done at this point. See resolve_item_baselines function.
518 
519     // Step 2.
520 
521     // The track sizing algorithm requires us to iterate through the items in ascendeding order of the number of
522     // tracks they span (first items that span 1 track, then items that span 2 tracks, etc).
523     // To avoid having to do multiple iterations of the items, we pre-sort them into this order.
524     items.sort_by(cmp_by_cross_flex_then_span_then_start(axis));
525 
526     // Step 2, Step 3 and Step 4
527     // 2 & 3. Iterate over items that don't cross a flex track. Items should have already been sorted in ascending order
528     // of the number of tracks they span. Step 2 is the 1 track case and has an optimised implementation
529     // 4. Next, repeat the previous step instead considering (together, rather than grouped by span size) all items
530     // that do span a track with a flexible sizing function while
531 
532     // Compute item's intrinsic (content-based) sizes
533     // Note: For items with a specified minimum size of auto (the initial value), the minimum contribution is usually equivalent
534     // to the min-content contribution—but can differ in some cases, see §6.6 Automatic Minimum Size of Grid Items.
535     // Also, minimum contribution <= min-content contribution <= max-content contribution.
536 
537     let axis_inner_node_size = inner_node_size.get(axis);
538     let flex_factor_sum = axis_tracks.iter().map(|track| track.flex_factor()).sum::<f32>();
539     let mut item_sizer =
540         IntrisicSizeMeasurer { tree, other_axis_tracks, axis, inner_node_size, get_track_size_estimate };
541 
542     let mut batched_item_iterator = ItemBatcher::new(axis);
543     while let Some((batch, is_flex)) = batched_item_iterator.next(items) {
544         // 2. Size tracks to fit non-spanning items: For each track with an intrinsic track sizing function and not a flexible sizing function,
545         // consider the items in it with a span of 1:
546         let batch_span = batch[0].placement(axis).span();
547         if !is_flex && batch_span == 1 {
548             for item in batch.iter_mut() {
549                 let track_index = item.placement_indexes(axis).start + 1;
550                 let track = &axis_tracks[track_index as usize];
551 
552                 // Handle base sizes
553                 let new_base_size = match track.min_track_sizing_function {
554                     MinTrackSizingFunction::MinContent => {
555                         f32_max(track.base_size, item_sizer.min_content_contribution(item))
556                     }
557                     // If the container size is indefinite and has not yet been resolved then percentage sized
558                     // tracks should be treated as min-content (this matches Chrome's behaviour and seems sensible)
559                     MinTrackSizingFunction::Fixed(LengthPercentage::Percent(_)) => {
560                         if axis_inner_node_size.is_none() {
561                             f32_max(track.base_size, item_sizer.min_content_contribution(item))
562                         } else {
563                             track.base_size
564                         }
565                     }
566                     MinTrackSizingFunction::MaxContent => {
567                         f32_max(track.base_size, item_sizer.max_content_contribution(item))
568                     }
569                     MinTrackSizingFunction::Auto => {
570                         let space = match axis_available_grid_space {
571                             // QUIRK: The spec says that:
572                             //
573                             //   If the grid container is being sized under a min- or max-content constraint, use the items’ limited
574                             //   min-content contributions in place of their minimum contributions here.
575                             //
576                             // However, in practice browsers only seem to apply this rule if the item is not a scroll container
577                             // (note that overflow:hidden counts as a scroll container), giving the automatic minimum size of scroll
578                             // containers (zero) precedence over the min-content contributions.
579                             AvailableSpace::MinContent | AvailableSpace::MaxContent
580                                 if !item.overflow.get(axis).is_scroll_container() =>
581                             {
582                                 let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
583                                 let axis_min_content_size = item_sizer.min_content_contribution(item);
584                                 let limit = track.max_track_sizing_function.definite_limit(axis_inner_node_size);
585                                 axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
586                             }
587                             _ => item_sizer.minimum_contribution(item, axis_tracks),
588                         };
589                         f32_max(track.base_size, space)
590                     }
591                     MinTrackSizingFunction::Fixed(_) => {
592                         // Do nothing as it's not an intrinsic track sizing function
593                         track.base_size
594                     }
595                 };
596                 let track = &mut axis_tracks[track_index as usize];
597                 track.base_size = new_base_size;
598 
599                 // Handle growth limits
600                 if let MaxTrackSizingFunction::FitContent(_) = track.max_track_sizing_function {
601                     // If item is not a scroll container, then increase the growth limit to at least the
602                     // size of the min-content contribution
603                     if !item.overflow.get(axis).is_scroll_container() {
604                         let min_content_contribution = item_sizer.min_content_contribution(item);
605                         track.growth_limit_planned_increase =
606                             f32_max(track.growth_limit_planned_increase, min_content_contribution);
607                     }
608 
609                     // Always increase the growth limit to at least the size of the *fit-content limited*
610                     // max-cotent contribution
611                     let fit_content_limit = track.fit_content_limit(axis_inner_node_size);
612                     let max_content_contribution =
613                         f32_min(item_sizer.max_content_contribution(item), fit_content_limit);
614                     track.growth_limit_planned_increase =
615                         f32_max(track.growth_limit_planned_increase, max_content_contribution);
616                 } else if track.max_track_sizing_function.is_max_content_alike()
617                     || track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none()
618                 {
619                     // If the container size is indefinite and has not yet been resolved then percentage sized
620                     // tracks should be treated as auto (this matches Chrome's behaviour and seems sensible)
621                     track.growth_limit_planned_increase =
622                         f32_max(track.growth_limit_planned_increase, item_sizer.max_content_contribution(item));
623                 } else if track.max_track_sizing_function.is_intrinsic() {
624                     track.growth_limit_planned_increase =
625                         f32_max(track.growth_limit_planned_increase, item_sizer.min_content_contribution(item));
626                 }
627             }
628 
629             for track in axis_tracks.iter_mut() {
630                 if track.growth_limit_planned_increase > 0.0 {
631                     track.growth_limit = if track.growth_limit == f32::INFINITY {
632                         track.growth_limit_planned_increase
633                     } else {
634                         f32_max(track.growth_limit, track.growth_limit_planned_increase)
635                     };
636                 }
637                 track.infinitely_growable = false;
638                 track.growth_limit_planned_increase = 0.0;
639                 if track.growth_limit < track.base_size {
640                     track.growth_limit = track.base_size;
641                 }
642             }
643 
644             continue;
645         }
646 
647         let use_flex_factor_for_distribution = is_flex && flex_factor_sum != 0.0;
648 
649         // 1. For intrinsic minimums:
650         // First increase the base size of tracks with an intrinsic min track sizing function
651         let has_intrinsic_min_track_sizing_function =
652             move |track: &GridTrack| track.min_track_sizing_function.definite_value(axis_inner_node_size).is_none();
653         for item in batch.iter_mut().filter(|item| item.crosses_intrinsic_track(axis)) {
654             // ...by distributing extra space as needed to accommodate these items’ minimum contributions.
655             //
656             // QUIRK: The spec says that:
657             //
658             //   If the grid container is being sized under a min- or max-content constraint, use the items’ limited min-content contributions
659             //   in place of their minimum contributions here.
660             //
661             // However, in practice browsers only seem to apply this rule if the item is not a scroll container (note that overflow:hidden counts as
662             // a scroll container), giving the automatic minimum size of scroll containers (zero) precedence over the min-content contributions.
663             let space = match axis_available_grid_space {
664                 AvailableSpace::MinContent | AvailableSpace::MaxContent
665                     if !item.overflow.get(axis).is_scroll_container() =>
666                 {
667                     let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
668                     let axis_min_content_size = item_sizer.min_content_contribution(item);
669                     let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size);
670                     axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
671                 }
672                 _ => item_sizer.minimum_contribution(item, axis_tracks),
673             };
674             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
675             if space > 0.0 {
676                 if item.overflow.get(axis).is_scroll_container() {
677                     let fit_content_limit =
678                         move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
679                     distribute_item_space_to_base_size(
680                         is_flex,
681                         use_flex_factor_for_distribution,
682                         space,
683                         tracks,
684                         has_intrinsic_min_track_sizing_function,
685                         fit_content_limit,
686                         IntrinsicContributionType::Minimum,
687                     );
688                 } else {
689                     distribute_item_space_to_base_size(
690                         is_flex,
691                         use_flex_factor_for_distribution,
692                         space,
693                         tracks,
694                         has_intrinsic_min_track_sizing_function,
695                         |track| track.growth_limit,
696                         IntrinsicContributionType::Minimum,
697                     );
698                 }
699             }
700         }
701         flush_planned_base_size_increases(axis_tracks);
702 
703         // 2. For content-based minimums:
704         // Next continue to increase the base size of tracks with a min track sizing function of min-content or max-content
705         // by distributing extra space as needed to account for these items' min-content contributions.
706         let has_min_or_max_content_min_track_sizing_function = move |track: &GridTrack| {
707             use MinTrackSizingFunction::{MaxContent, MinContent};
708             matches!(track.min_track_sizing_function, MinContent | MaxContent)
709         };
710         for item in batch.iter_mut() {
711             let space = item_sizer.min_content_contribution(item);
712             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
713             if space > 0.0 {
714                 if item.overflow.get(axis).is_scroll_container() {
715                     let fit_content_limit =
716                         move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
717                     distribute_item_space_to_base_size(
718                         is_flex,
719                         use_flex_factor_for_distribution,
720                         space,
721                         tracks,
722                         has_min_or_max_content_min_track_sizing_function,
723                         fit_content_limit,
724                         IntrinsicContributionType::Minimum,
725                     );
726                 } else {
727                     distribute_item_space_to_base_size(
728                         is_flex,
729                         use_flex_factor_for_distribution,
730                         space,
731                         tracks,
732                         has_min_or_max_content_min_track_sizing_function,
733                         |track| track.growth_limit,
734                         IntrinsicContributionType::Minimum,
735                     );
736                 }
737             }
738         }
739         flush_planned_base_size_increases(axis_tracks);
740 
741         // 3. For max-content minimums:
742 
743         // If the grid container is being sized under a max-content constraint, continue to increase the base size of tracks with
744         // a min track sizing function of auto or max-content by distributing extra space as needed to account for these items'
745         // limited max-content contributions.
746 
747         // Define fit_content_limited_growth_limit function. This is passed to the distribute_space_up_to_limits
748         // helper function, and is used to compute the limit to distribute up to for each track.
749         // Wrapping the method on GridTrack is necessary in order to resolve percentage fit-content arguments.
750         if axis_available_grid_space == AvailableSpace::MaxContent {
751             /// Whether a track:
752             ///   - has an Auto MIN track sizing function
753             ///   - Does not have a MinContent MAX track sizing function
754             ///
755             /// The latter condition was added in order to match Chrome. But I believe it is due to the provision
756             /// under minmax here https://www.w3.org/TR/css-grid-1/#track-sizes which states that:
757             ///
758             ///    "If the max is less than the min, then the max will be floored by the min (essentially yielding minmax(min, min))"
759             #[inline(always)]
760             fn has_auto_min_track_sizing_function(track: &GridTrack) -> bool {
761                 track.min_track_sizing_function == MinTrackSizingFunction::Auto
762                     && track.max_track_sizing_function != MaxTrackSizingFunction::MinContent
763             }
764 
765             /// Whether a track has a MaxContent min track sizing function
766             #[inline(always)]
767             fn has_max_content_min_track_sizing_function(track: &GridTrack) -> bool {
768                 track.min_track_sizing_function == MinTrackSizingFunction::MaxContent
769             }
770 
771             for item in batch.iter_mut() {
772                 let axis_max_content_size = item_sizer.max_content_contribution(item);
773                 let limit = item.spanned_track_limit(axis, axis_tracks, axis_inner_node_size);
774                 let space = axis_max_content_size.maybe_min(limit);
775                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
776                 if space > 0.0 {
777                     // If any of the tracks spanned by the item have a MaxContent min track sizing function then
778                     // distribute space only to those tracks. Otherwise distribute space to tracks with an Auto min
779                     // track sizing function.
780                     //
781                     // Note: this prioritisation of MaxContent over Auto is not mentioned in the spec (which suggests that
782                     // we ought to distribute space evenly between MaxContent and Auto tracks). But it is implemented like
783                     // this in both Chrome and Firefox (and it does have a certain logic to it), so we implement it too for
784                     // compatibility.
785                     //
786                     // See: https://www.w3.org/TR/css-grid-1/#track-size-max-content-min
787                     if tracks.iter().any(has_max_content_min_track_sizing_function) {
788                         distribute_item_space_to_base_size(
789                             is_flex,
790                             use_flex_factor_for_distribution,
791                             space,
792                             tracks,
793                             has_max_content_min_track_sizing_function,
794                             |_| f32::INFINITY,
795                             IntrinsicContributionType::Maximum,
796                         );
797                     } else {
798                         let fit_content_limited_growth_limit =
799                             move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
800                         distribute_item_space_to_base_size(
801                             is_flex,
802                             use_flex_factor_for_distribution,
803                             space,
804                             tracks,
805                             has_auto_min_track_sizing_function,
806                             fit_content_limited_growth_limit,
807                             IntrinsicContributionType::Maximum,
808                         );
809                     }
810                 }
811             }
812             flush_planned_base_size_increases(axis_tracks);
813         }
814 
815         // In all cases, continue to increase the base size of tracks with a min track sizing function of max-content by distributing
816         // extra space as needed to account for these items' max-content contributions.
817         let has_max_content_min_track_sizing_function =
818             move |track: &GridTrack| matches!(track.min_track_sizing_function, MinTrackSizingFunction::MaxContent);
819         for item in batch.iter_mut() {
820             let axis_max_content_size = item_sizer.max_content_contribution(item);
821             let space = axis_max_content_size;
822             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
823             if space > 0.0 {
824                 distribute_item_space_to_base_size(
825                     is_flex,
826                     use_flex_factor_for_distribution,
827                     space,
828                     tracks,
829                     has_max_content_min_track_sizing_function,
830                     |track| track.growth_limit,
831                     IntrinsicContributionType::Maximum,
832                 );
833             }
834         }
835         flush_planned_base_size_increases(axis_tracks);
836 
837         // 4. If at this point any track’s growth limit is now less than its base size, increase its growth limit to match its base size.
838         for track in axis_tracks.iter_mut() {
839             if track.growth_limit < track.base_size {
840                 track.growth_limit = track.base_size;
841             }
842         }
843 
844         // If a track is a flexible track, then it has flexible max track sizing function
845         // It cannot also have an intrinsic max track sizing function, so these steps do not apply.
846         if !is_flex {
847             // 5. For intrinsic maximums: Next increase the growth limit of tracks with an intrinsic max track sizing function by
848             // distributing extra space as needed to account for these items' min-content contributions.
849             let has_intrinsic_max_track_sizing_function =
850                 move |track: &GridTrack| track.max_track_sizing_function.definite_value(axis_inner_node_size).is_none();
851             for item in batch.iter_mut() {
852                 let axis_min_content_size = item_sizer.min_content_contribution(item);
853                 let space = axis_min_content_size;
854                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
855                 if space > 0.0 {
856                     distribute_item_space_to_growth_limit(
857                         space,
858                         tracks,
859                         has_intrinsic_max_track_sizing_function,
860                         inner_node_size.get(axis),
861                     );
862                 }
863             }
864             // Mark any tracks whose growth limit changed from infinite to finite in this step as infinitely growable for the next step.
865             flush_planned_growth_limit_increases(axis_tracks, true);
866 
867             // 6. For max-content maximums: Lastly continue to increase the growth limit of tracks with a max track sizing function of max-content
868             // by distributing extra space as needed to account for these items' max-content contributions. However, limit the growth of any
869             // fit-content() tracks by their fit-content() argument.
870             let has_max_content_max_track_sizing_function = |track: &GridTrack| {
871                 track.max_track_sizing_function.is_max_content_alike()
872                     || (track.max_track_sizing_function.uses_percentage() && axis_inner_node_size.is_none())
873             };
874             for item in batch.iter_mut() {
875                 let axis_max_content_size = item_sizer.max_content_contribution(item);
876                 let space = axis_max_content_size;
877                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
878                 if space > 0.0 {
879                     distribute_item_space_to_growth_limit(
880                         space,
881                         tracks,
882                         has_max_content_max_track_sizing_function,
883                         inner_node_size.get(axis),
884                     );
885                 }
886             }
887             // Mark any tracks whose growth limit changed from infinite to finite in this step as infinitely growable for the next step.
888             flush_planned_growth_limit_increases(axis_tracks, false);
889         }
890     }
891 
892     // Step 5. If any track still has an infinite growth limit (because, for example, it had no items placed
893     // in it or it is a flexible track), set its growth limit to its base size.
894     // NOTE: this step is super-important to ensure that the "Maximise Tracks" step doesn't affect flexible tracks
895     axis_tracks
896         .iter_mut()
897         .filter(|track| track.growth_limit == f32::INFINITY)
898         .for_each(|track| track.growth_limit = track.base_size);
899 }
900 
901 /// 11.5.1. Distributing Extra Space Across Spanned Tracks
902 /// https://www.w3.org/TR/css-grid-1/#extra-space
903 #[inline(always)]
distribute_item_space_to_base_size( is_flex: bool, use_flex_factor_for_distribution: bool, space: f32, tracks: &mut [GridTrack], track_is_affected: impl Fn(&GridTrack) -> bool, track_limit: impl Fn(&GridTrack) -> f32, intrinsic_contribution_type: IntrinsicContributionType, )904 fn distribute_item_space_to_base_size(
905     is_flex: bool,
906     use_flex_factor_for_distribution: bool,
907     space: f32,
908     tracks: &mut [GridTrack],
909     track_is_affected: impl Fn(&GridTrack) -> bool,
910     track_limit: impl Fn(&GridTrack) -> f32,
911     intrinsic_contribution_type: IntrinsicContributionType,
912 ) {
913     if is_flex {
914         let filter = |track: &GridTrack| track.is_flexible() && track_is_affected(track);
915         if use_flex_factor_for_distribution {
916             distribute_item_space_to_base_size_inner(
917                 space,
918                 tracks,
919                 filter,
920                 |track| track.flex_factor(),
921                 track_limit,
922                 intrinsic_contribution_type,
923             )
924         } else {
925             distribute_item_space_to_base_size_inner(
926                 space,
927                 tracks,
928                 filter,
929                 |_| 1.0,
930                 track_limit,
931                 intrinsic_contribution_type,
932             )
933         }
934     } else {
935         distribute_item_space_to_base_size_inner(
936             space,
937             tracks,
938             track_is_affected,
939             |_| 1.0,
940             track_limit,
941             intrinsic_contribution_type,
942         )
943     }
944 
945     /// Inner function that doesn't account for differences due to distributing to flex items
946     /// This difference is handled by the closure passed in above
947     fn distribute_item_space_to_base_size_inner(
948         space: f32,
949         tracks: &mut [GridTrack],
950         track_is_affected: impl Fn(&GridTrack) -> bool,
951         track_distribution_proportion: impl Fn(&GridTrack) -> f32,
952         track_limit: impl Fn(&GridTrack) -> f32,
953         intrinsic_contribution_type: IntrinsicContributionType,
954     ) {
955         // Skip this distribution if there is either
956         //   - no space to distribute
957         //   - no affected tracks to distribute space to
958         if space == 0.0 || !tracks.iter().any(&track_is_affected) {
959             return;
960         }
961 
962         // Define get_base_size function. This is passed to the distribute_space_up_to_limits helper function
963         // to indicate that it is the base size that is being distributed to.
964         let get_base_size = |track: &GridTrack| track.base_size;
965 
966         // 1. Find the space to distribute
967         let track_sizes: f32 = tracks.iter().map(|track| track.base_size).sum();
968         let extra_space: f32 = f32_max(0.0, space - track_sizes);
969 
970         // 2. Distribute space up to limits:
971         // Note: there are two exit conditions to this loop:
972         //   - We run out of space to distribute (extra_space falls below THRESHOLD)
973         //   - We run out of growable tracks to distribute to
974 
975         /// Define a small constant to avoid infinite loops due to rounding errors. Rather than stopping distributing
976         /// extra space when it gets to exactly zero, we will stop when it falls below this amount
977         const THRESHOLD: f32 = 0.000001;
978 
979         let extra_space = distribute_space_up_to_limits(
980             extra_space,
981             tracks,
982             &track_is_affected,
983             &track_distribution_proportion,
984             get_base_size,
985             &track_limit,
986         );
987 
988         // 3. Distribute remaining span beyond limits (if any)
989         if extra_space > THRESHOLD {
990             // When accommodating minimum contributions or accommodating min-content contributions:
991             //   - any affected track that happens to also have an intrinsic max track sizing function;
992             // When accommodating max-content contributions:
993             //   - any affected track that happens to also have a max-content max track sizing function
994             let mut filter = match intrinsic_contribution_type {
995                 IntrinsicContributionType::Minimum => {
996                     (|track: &GridTrack| track.max_track_sizing_function.is_intrinsic()) as fn(&GridTrack) -> bool
997                 }
998                 IntrinsicContributionType::Maximum => {
999                     use MaxTrackSizingFunction::{FitContent, MaxContent};
1000                     (|track: &GridTrack| {
1001                         matches!(track.max_track_sizing_function, MaxContent | FitContent(_))
1002                             || track.min_track_sizing_function == MinTrackSizingFunction::MaxContent
1003                     }) as fn(&GridTrack) -> bool
1004                 }
1005             };
1006 
1007             // If there are no such tracks (matching filter above), then use all affected tracks.
1008             let number_of_tracks =
1009                 tracks.iter().filter(|track| track_is_affected(track)).filter(|track| filter(track)).count();
1010             if number_of_tracks == 0 {
1011                 filter = (|_| true) as fn(&GridTrack) -> bool;
1012             }
1013 
1014             distribute_space_up_to_limits(
1015                 extra_space,
1016                 tracks,
1017                 filter,
1018                 &track_distribution_proportion,
1019                 get_base_size,
1020                 &track_limit, // Should apply only fit-content limit here?
1021             );
1022         }
1023 
1024         // 4. For each affected track, if the track’s item-incurred increase is larger than the track’s planned increase
1025         // set the track’s planned increase to that value.
1026         for track in tracks.iter_mut() {
1027             if track.item_incurred_increase > track.base_size_planned_increase {
1028                 track.base_size_planned_increase = track.item_incurred_increase;
1029             }
1030 
1031             // Reset the item_incurresed increase ready for the next space distribution
1032             track.item_incurred_increase = 0.0;
1033         }
1034     }
1035 }
1036 
1037 /// 11.5.1. Distributing Extra Space Across Spanned Tracks
1038 /// This is simplified (and faster) version of the algorithm for growth limits
1039 /// https://www.w3.org/TR/css-grid-1/#extra-space
distribute_item_space_to_growth_limit( space: f32, tracks: &mut [GridTrack], track_is_affected: impl Fn(&GridTrack) -> bool, axis_inner_node_size: Option<f32>, )1040 fn distribute_item_space_to_growth_limit(
1041     space: f32,
1042     tracks: &mut [GridTrack],
1043     track_is_affected: impl Fn(&GridTrack) -> bool,
1044     axis_inner_node_size: Option<f32>,
1045 ) {
1046     // Skip this distribution if there is either
1047     //   - no space to distribute
1048     //   - no affected tracks to distribute space to
1049     if space == 0.0 || tracks.iter().filter(|track| track_is_affected(track)).count() == 0 {
1050         return;
1051     }
1052 
1053     // 1. Find the space to distribute
1054     let track_sizes: f32 = tracks
1055         .iter()
1056         .map(|track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit })
1057         .sum();
1058     let extra_space: f32 = f32_max(0.0, space - track_sizes);
1059 
1060     // 2. Distribute space up to limits:
1061     // For growth limits the limit is either Infinity, or the growth limit itself. Which means that:
1062     //   - If there are any tracks with infinite limits then all space will be distributed to those track(s).
1063     //   - Otherwise no space will be distributed as part of this step
1064     let number_of_growable_tracks = tracks
1065         .iter()
1066         .filter(|track| track_is_affected(track))
1067         .filter(|track| {
1068             track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1069         })
1070         .count();
1071     if number_of_growable_tracks > 0 {
1072         let item_incurred_increase = extra_space / number_of_growable_tracks as f32;
1073         for track in tracks.iter_mut().filter(|track| track_is_affected(track)).filter(|track| {
1074             track.infinitely_growable || track.fit_content_limited_growth_limit(axis_inner_node_size) == f32::INFINITY
1075         }) {
1076             track.item_incurred_increase = item_incurred_increase;
1077         }
1078     } else {
1079         // 3. Distribute space beyond limits
1080         // If space remains after all tracks are frozen, unfreeze and continue to distribute space to the item-incurred increase
1081         // ...when handling any intrinsic growth limit: all affected tracks.
1082         distribute_space_up_to_limits(
1083             extra_space,
1084             tracks,
1085             track_is_affected,
1086             |_| 1.0,
1087             |track| if track.growth_limit == f32::INFINITY { track.base_size } else { track.growth_limit },
1088             move |track| track.fit_content_limit(axis_inner_node_size),
1089         );
1090     };
1091 
1092     // 4. For each affected track, if the track’s item-incurred increase is larger than the track’s planned increase
1093     // set the track’s planned increase to that value.
1094     for track in tracks.iter_mut() {
1095         if track.item_incurred_increase > track.growth_limit_planned_increase {
1096             track.growth_limit_planned_increase = track.item_incurred_increase;
1097         }
1098 
1099         // Reset the item_incurresed increase ready for the next space distribution
1100         track.item_incurred_increase = 0.0;
1101     }
1102 }
1103 
1104 /// 11.6 Maximise Tracks
1105 /// Distributes free space (if any) to tracks with FINITE growth limits, up to their limits.
1106 #[inline(always)]
maximise_tracks( axis_tracks: &mut [GridTrack], axis_inner_node_size: Option<f32>, axis_available_grid_space: AvailableSpace, )1107 fn maximise_tracks(
1108     axis_tracks: &mut [GridTrack],
1109     axis_inner_node_size: Option<f32>,
1110     axis_available_grid_space: AvailableSpace,
1111 ) {
1112     let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1113     let free_space = axis_available_grid_space.compute_free_space(used_space);
1114     if free_space == f32::INFINITY {
1115         axis_tracks.iter_mut().for_each(|track| track.base_size = track.growth_limit);
1116     } else if free_space > 0.0 {
1117         distribute_space_up_to_limits(
1118             free_space,
1119             axis_tracks,
1120             |_| true,
1121             |_| 1.0,
1122             |track| track.base_size,
1123             move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size),
1124         );
1125         for track in axis_tracks.iter_mut() {
1126             track.base_size += track.item_incurred_increase;
1127             track.item_incurred_increase = 0.0;
1128         }
1129     }
1130 }
1131 
1132 /// 11.7. Expand Flexible Tracks
1133 /// This step sizes flexible tracks using the largest value it can assign to an fr without exceeding the available space.
1134 #[allow(clippy::too_many_arguments)]
1135 #[inline(always)]
expand_flexible_tracks( tree: &mut impl LayoutPartialTree, axis: AbstractAxis, axis_tracks: &mut [GridTrack], items: &mut [GridItem], axis_min_size: Option<f32>, axis_max_size: Option<f32>, axis_available_space_for_expansion: AvailableSpace, inner_node_size: Size<Option<f32>>, )1136 fn expand_flexible_tracks(
1137     tree: &mut impl LayoutPartialTree,
1138     axis: AbstractAxis,
1139     axis_tracks: &mut [GridTrack],
1140     items: &mut [GridItem],
1141     axis_min_size: Option<f32>,
1142     axis_max_size: Option<f32>,
1143     axis_available_space_for_expansion: AvailableSpace,
1144     inner_node_size: Size<Option<f32>>,
1145 ) {
1146     // First, find the grid’s used flex fraction:
1147     let flex_fraction = match axis_available_space_for_expansion {
1148         // If the free space is zero:
1149         //    The used flex fraction is zero.
1150         // Otherwise, if the free space is a definite length:
1151         //   The used flex fraction is the result of finding the size of an fr using all of the grid tracks and
1152         //   a space to fill of the available grid space.
1153         AvailableSpace::Definite(available_space) => {
1154             let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1155             let free_space = available_space - used_space;
1156             if free_space <= 0.0 {
1157                 0.0
1158             } else {
1159                 find_size_of_fr(axis_tracks, available_space)
1160             }
1161         }
1162         // If ... sizing the grid container under a min-content constraint the used flex fraction is zero.
1163         AvailableSpace::MinContent => 0.0,
1164         // Otherwise, if the free space is an indefinite length:
1165         AvailableSpace::MaxContent => {
1166             // The used flex fraction is the maximum of:
1167             let flex_fraction = f32_max(
1168                 // For each flexible track, if the flexible track’s flex factor is greater than one,
1169                 // the result of dividing the track’s base size by its flex factor; otherwise, the track’s base size.
1170                 axis_tracks
1171                     .iter()
1172                     .filter(|track| track.max_track_sizing_function.is_flexible())
1173                     .map(|track| {
1174                         let flex_factor = track.flex_factor();
1175                         if flex_factor > 1.0 {
1176                             track.base_size / flex_factor
1177                         } else {
1178                             track.base_size
1179                         }
1180                     })
1181                     .max_by(|a, b| a.total_cmp(b))
1182                     .unwrap_or(0.0),
1183                 // For each grid item that crosses a flexible track, the result of finding the size of an fr using all the grid tracks
1184                 // that the item crosses and a space to fill of the item’s max-content contribution.
1185                 items
1186                     .iter_mut()
1187                     .filter(|item| item.crosses_flexible_track(axis))
1188                     .map(|item| {
1189                         let tracks = &axis_tracks[item.track_range_excluding_lines(axis)];
1190                         // TODO: plumb estimate of other axis size (known_dimensions) in here rather than just passing Size::NONE?
1191                         let max_content_contribution =
1192                             item.max_content_contribution_cached(axis, tree, Size::NONE, inner_node_size);
1193                         find_size_of_fr(tracks, max_content_contribution)
1194                     })
1195                     .max_by(|a, b| a.total_cmp(b))
1196                     .unwrap_or(0.0),
1197             );
1198 
1199             // If using this flex fraction would cause the grid to be smaller than the grid container’s min-width/height (or larger than the
1200             // grid container’s max-width/height), then redo this step, treating the free space as definite and the available grid space as equal
1201             // to the grid container’s inner size when it’s sized to its min-width/height (max-width/height).
1202             // (Note: min_size takes precedence over max_size)
1203             let hypothetical_grid_size: f32 = axis_tracks
1204                 .iter()
1205                 .map(|track| match track.max_track_sizing_function {
1206                     MaxTrackSizingFunction::Fraction(track_flex_factor) => {
1207                         f32_max(track.base_size, track_flex_factor * flex_fraction)
1208                     }
1209                     _ => track.base_size,
1210                 })
1211                 .sum();
1212             let axis_min_size = axis_min_size.unwrap_or(0.0);
1213             let axis_max_size = axis_max_size.unwrap_or(f32::INFINITY);
1214             if hypothetical_grid_size < axis_min_size {
1215                 find_size_of_fr(axis_tracks, axis_min_size)
1216             } else if hypothetical_grid_size > axis_max_size {
1217                 find_size_of_fr(axis_tracks, axis_max_size)
1218             } else {
1219                 flex_fraction
1220             }
1221         }
1222     };
1223 
1224     // For each flexible track, if the product of the used flex fraction and the track’s flex factor is greater
1225     // than the track’s base size, set its base size to that product.
1226     for track in axis_tracks.iter_mut() {
1227         if let MaxTrackSizingFunction::Fraction(track_flex_factor) = track.max_track_sizing_function {
1228             track.base_size = f32_max(track.base_size, track_flex_factor * flex_fraction);
1229         }
1230     }
1231 }
1232 
1233 /// 11.7.1. Find the Size of an fr
1234 /// This algorithm finds the largest size that an fr unit can be without exceeding the target size.
1235 /// It must be called with a set of grid tracks and some quantity of space to fill.
1236 #[inline(always)]
find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f321237 fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
1238     // Handle the trivial case where there is no space to fill
1239     // Do not remove as otherwise the loop below will loop infinitely
1240     if space_to_fill == 0.0 {
1241         return 0.0;
1242     }
1243 
1244     // If the product of the hypothetical fr size (computed below) and any flexible track’s flex factor
1245     // is less than the track’s base size, then we must restart this algorithm treating all such tracks as inflexible.
1246     // We therefore wrap the entire algorithm in a loop, with an hypothetical_fr_size of INFINITY such that the above
1247     // condition can never be true for the first iteration.
1248     let mut hypothetical_fr_size = f32::INFINITY;
1249     let mut previous_iter_hypothetical_fr_size;
1250     loop {
1251         // Let leftover space be the space to fill minus the base sizes of the non-flexible grid tracks.
1252         // Let flex factor sum be the sum of the flex factors of the flexible tracks. If this value is less than 1, set it to 1 instead.
1253         // We compute both of these in a single loop to avoid iterating over the data twice
1254         let mut used_space = 0.0;
1255         let mut naive_flex_factor_sum = 0.0;
1256         for track in tracks.iter() {
1257             match track.max_track_sizing_function {
1258                 // Tracks for which flex_factor * hypothetical_fr_size < track.base_size are treated as inflexible
1259                 MaxTrackSizingFunction::Fraction(flex_factor)
1260                     if flex_factor * hypothetical_fr_size >= track.base_size =>
1261                 {
1262                     naive_flex_factor_sum += flex_factor;
1263                 }
1264                 _ => used_space += track.base_size,
1265             };
1266         }
1267         let leftover_space = space_to_fill - used_space;
1268         let flex_factor = f32_max(naive_flex_factor_sum, 1.0);
1269 
1270         // Let the hypothetical fr size be the leftover space divided by the flex factor sum.
1271         previous_iter_hypothetical_fr_size = hypothetical_fr_size;
1272         hypothetical_fr_size = leftover_space / flex_factor;
1273 
1274         // If the product of the hypothetical fr size and a flexible track’s flex factor is less than the track’s base size,
1275         // restart this algorithm treating all such tracks as inflexible.
1276         // We keep track of the hypothetical_fr_size
1277         let hypothetical_fr_size_is_valid = tracks.iter().all(|track| match track.max_track_sizing_function {
1278             MaxTrackSizingFunction::Fraction(flex_factor) => {
1279                 flex_factor * hypothetical_fr_size >= track.base_size
1280                     || flex_factor * previous_iter_hypothetical_fr_size < track.base_size
1281             }
1282             _ => true,
1283         });
1284         if hypothetical_fr_size_is_valid {
1285             break;
1286         }
1287     }
1288 
1289     // Return the hypothetical fr size.
1290     hypothetical_fr_size
1291 }
1292 
1293 /// 11.8. Stretch auto Tracks
1294 /// This step expands tracks that have an auto max track sizing function by dividing any remaining positive, definite free space equally amongst them.
1295 #[inline(always)]
stretch_auto_tracks( axis_tracks: &mut [GridTrack], axis_min_size: Option<f32>, axis_available_space_for_expansion: AvailableSpace, )1296 fn stretch_auto_tracks(
1297     axis_tracks: &mut [GridTrack],
1298     axis_min_size: Option<f32>,
1299     axis_available_space_for_expansion: AvailableSpace,
1300 ) {
1301     let num_auto_tracks =
1302         axis_tracks.iter().filter(|track| track.max_track_sizing_function == MaxTrackSizingFunction::Auto).count();
1303     if num_auto_tracks > 0 {
1304         let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
1305 
1306         // If the free space is indefinite, but the grid container has a definite min-width/height
1307         // use that size to calculate the free space for this step instead.
1308         let free_space = if axis_available_space_for_expansion.is_definite() {
1309             axis_available_space_for_expansion.compute_free_space(used_space)
1310         } else {
1311             match axis_min_size {
1312                 Some(size) => size - used_space,
1313                 None => 0.0,
1314             }
1315         };
1316         if free_space > 0.0 {
1317             let extra_space_per_auto_track = free_space / num_auto_tracks as f32;
1318             axis_tracks
1319                 .iter_mut()
1320                 .filter(|track| track.max_track_sizing_function == MaxTrackSizingFunction::Auto)
1321                 .for_each(|track| track.base_size += extra_space_per_auto_track);
1322         }
1323     }
1324 }
1325 
1326 /// Helper function for distributing space to tracks evenly
1327 /// Used by both distribute_item_space_to_base_size and maximise_tracks steps
1328 #[inline(always)]
distribute_space_up_to_limits( space_to_distribute: f32, tracks: &mut [GridTrack], track_is_affected: impl Fn(&GridTrack) -> bool, track_distribution_proportion: impl Fn(&GridTrack) -> f32, track_affected_property: impl Fn(&GridTrack) -> f32, track_limit: impl Fn(&GridTrack) -> f32, ) -> f321329 fn distribute_space_up_to_limits(
1330     space_to_distribute: f32,
1331     tracks: &mut [GridTrack],
1332     track_is_affected: impl Fn(&GridTrack) -> bool,
1333     track_distribution_proportion: impl Fn(&GridTrack) -> f32,
1334     track_affected_property: impl Fn(&GridTrack) -> f32,
1335     track_limit: impl Fn(&GridTrack) -> f32,
1336 ) -> f32 {
1337     /// Define a small constant to avoid infinite loops due to rounding errors. Rather than stopping distributing
1338     /// extra space when it gets to exactly zero, we will stop when it falls below this amount
1339     const THRESHOLD: f32 = 0.01;
1340 
1341     let mut space_to_distribute = space_to_distribute;
1342     while space_to_distribute > THRESHOLD {
1343         let track_distribution_proportion_sum: f32 = tracks
1344             .iter()
1345             .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1346             .filter(|track| track_is_affected(track))
1347             .map(&track_distribution_proportion)
1348             .sum();
1349 
1350         if track_distribution_proportion_sum == 0.0 {
1351             break;
1352         }
1353 
1354         // Compute item-incurred increase for this iteration
1355         let min_increase_limit = tracks
1356             .iter()
1357             .filter(|track| track_affected_property(track) + track.item_incurred_increase < track_limit(track))
1358             .filter(|track| track_is_affected(track))
1359             .map(|track| (track_limit(track) - track_affected_property(track)) / track_distribution_proportion(track))
1360             .min_by(|a, b| a.total_cmp(b))
1361             .unwrap(); // We will never pass an empty track list to this function
1362         let iteration_item_incurred_increase =
1363             f32_min(min_increase_limit, space_to_distribute / track_distribution_proportion_sum);
1364 
1365         for track in tracks.iter_mut().filter(|track| track_is_affected(track)) {
1366             let increase = iteration_item_incurred_increase * track_distribution_proportion(track);
1367             if increase > 0.0 && track_affected_property(track) + increase <= track_limit(track) + THRESHOLD {
1368                 track.item_incurred_increase += increase;
1369                 space_to_distribute -= increase;
1370             }
1371         }
1372     }
1373 
1374     space_to_distribute
1375 }
1376