Skip to main content

taffy/compute/grid/
explicit_grid.rs

1//! Helper functions for initialising GridTrack's from styles
2//! This mainly consists of evaluating GridAutoTracks
3use super::types::{GridTrack, GridTrackKind, TrackCounts};
4use crate::geometry::AbsoluteAxis;
5use crate::style::{LengthPercentage, RepetitionCount, TrackSizingFunction};
6use crate::style_helpers::TaffyAuto;
7use crate::util::sys::{ceil, floor, Vec};
8use crate::util::MaybeMath;
9use crate::util::ResolveOrZero;
10use core::cmp::min;
11
12use super::MAX_GRID_TRACKS;
13use crate::{GenericGridTemplateComponent, GenericRepetition, GridContainerStyle};
14
15/// The auto-repeat fit strategy to use
16pub(crate) enum AutoRepeatStrategy {
17    /// If the grid container has a definite size or max size in the relevant axis:
18    ///   - then the number of repetitions is the largest possible positive integer that does not cause the grid to overflow the content
19    ///     box of its grid container.
20    MaxRepetitionsThatDoNotOverflow,
21    /// Otherwise, if the grid container has a definite min size in the relevant axis:
22    ///   - then the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement
23    MinRepetitionsThatDoOverflow,
24}
25
26/// Compute the number of rows and columns in the explicit grid
27pub(crate) fn compute_explicit_grid_size_in_axis(
28    style: &impl GridContainerStyle,
29    auto_fit_container_size: Option<f32>,
30    auto_fit_strategy: AutoRepeatStrategy,
31    resolve_calc_value: impl Fn(*const (), f32) -> f32,
32    axis: AbsoluteAxis,
33) -> (u16, u16) {
34    let template = match axis {
35        AbsoluteAxis::Horizontal => style.grid_template_columns(),
36        AbsoluteAxis::Vertical => style.grid_template_rows(),
37    };
38    let Some(template) = template else {
39        return (0, 0);
40    };
41
42    // If template contains no tracks, then there are trivially zero explicit tracks
43    let track_count = template.len();
44    if track_count == 0 {
45        return (0, 0);
46    }
47
48    // If there are any repetitions that contains no tracks, then the whole definition should be considered invalid
49    // and we default to no explicit tracks
50    let template_has_repetitions_with_zero_tracks = template.clone().any(|track_def| match track_def {
51        GenericGridTemplateComponent::Single(_) => false,
52        GenericGridTemplateComponent::Repeat(repeat) => repeat.track_count() == 0,
53    });
54    if template_has_repetitions_with_zero_tracks {
55        return (0, 0);
56    }
57
58    // Compute that number of track generated by single track definition and repetitions with a fixed repetition count
59    // (clamping to the maximum track limit to prevent arithmetic overflow)
60    let non_auto_repeating_track_count = template
61        .clone()
62        .fold(0u32, |track_count, track_def| {
63            let additional_tracks = match track_def {
64                GenericGridTemplateComponent::Single(_) => 1u32,
65                GenericGridTemplateComponent::Repeat(repeat) => match repeat.count() {
66                    RepetitionCount::Count(count) => count as u32 * repeat.track_count() as u32,
67                    RepetitionCount::AutoFit | RepetitionCount::AutoFill => 0,
68                },
69            };
70            track_count.saturating_add(additional_tracks)
71        })
72        .min(MAX_GRID_TRACKS as u32) as u16;
73
74    let auto_repetition_count: u16 = template.clone().filter(|track_def| track_def.is_auto_repetition()).count() as u16;
75    let all_track_defs_have_fixed_component = template.clone().all(|track_def| match track_def {
76        GenericGridTemplateComponent::Single(sizing_function) => sizing_function.has_fixed_component(),
77        GenericGridTemplateComponent::Repeat(repeat) => {
78            repeat.tracks().all(|sizing_function| sizing_function.has_fixed_component())
79        }
80    });
81
82    let template_is_valid =
83        auto_repetition_count == 0 || (auto_repetition_count == 1 && all_track_defs_have_fixed_component);
84
85    // If the template is invalid because it contains multiple auto-repetition definitions or it combines an auto-repetition
86    // definition with non-fixed-size track sizing functions, then disregard it entirely and default to zero explicit tracks
87    if !template_is_valid {
88        return (0, 0);
89    }
90
91    // If there are no repetitions, then the number of explicit tracks is simply equal to the lengths of the track definition
92    // vector (as each item in the Vec represents one track).
93    if auto_repetition_count == 0 {
94        return (0, non_auto_repeating_track_count);
95    }
96
97    let mut auto_repeat_insertion_point = 0u32;
98    let repetition_definition = template
99        .clone()
100        .find_map(|def| match def {
101            GenericGridTemplateComponent::Single(_) => {
102                auto_repeat_insertion_point = auto_repeat_insertion_point.saturating_add(1);
103                None
104            }
105            GenericGridTemplateComponent::Repeat(repeat) => match repeat.count() {
106                RepetitionCount::Count(count) => {
107                    auto_repeat_insertion_point =
108                        auto_repeat_insertion_point.saturating_add(count as u32 * repeat.track_count() as u32);
109                    None
110                }
111                RepetitionCount::AutoFit | RepetitionCount::AutoFill => Some(repeat),
112            },
113        })
114        .unwrap();
115    let repetition_definition_iter = repetition_definition.tracks();
116    let repetition_track_count = repetition_definition_iter.len().min(u16::MAX as usize) as u16;
117
118    // Determine the number of repetitions
119    let num_repetitions: u32 = match auto_fit_container_size {
120        None => 1,
121        Some(inner_container_size) => {
122            let parent_size = Some(inner_container_size);
123
124            /// ...treating each track as its max track sizing function if that is definite or as its minimum track sizing function
125            /// otherwise, flooring the max track sizing function by the min track sizing function if both are definite
126            fn track_definite_value(
127                sizing_function: TrackSizingFunction,
128                parent_size: Option<f32>,
129                calc_resolver: impl Fn(*const (), f32) -> f32,
130            ) -> f32 {
131                let max_size = sizing_function.max.definite_value(parent_size, &calc_resolver);
132                let min_size = sizing_function.min.definite_value(parent_size, &calc_resolver);
133                max_size.map(|max| max.maybe_max(min_size)).or(min_size).unwrap()
134            }
135
136            let non_repeating_track_used_space: f32 = template
137                .clone()
138                .map(|track_def| match track_def {
139                    GenericGridTemplateComponent::Single(sizing_function) => {
140                        track_definite_value(sizing_function, parent_size, &resolve_calc_value)
141                    }
142                    GenericGridTemplateComponent::Repeat(repeat) => match repeat.count() {
143                        RepetitionCount::Count(count) => {
144                            let sum = repeat
145                                .tracks()
146                                .map(|sizing_function| {
147                                    track_definite_value(sizing_function, parent_size, &resolve_calc_value)
148                                })
149                                .sum::<f32>();
150                            sum * (count as f32)
151                        }
152                        RepetitionCount::AutoFit | RepetitionCount::AutoFill => 0.0,
153                    },
154                })
155                .sum();
156            let gap_size = style.gap().get_abs(axis).resolve_or_zero(Some(inner_container_size), &resolve_calc_value);
157
158            // Compute the amount of space that a single repetition of the repeated track list takes
159            let per_repetition_track_used_space: f32 = repetition_definition_iter
160                .map(|sizing_function| track_definite_value(sizing_function, parent_size, &resolve_calc_value))
161                .sum::<f32>();
162
163            // We special case the first repetition here because the number of gaps in the first repetition
164            // depends on the number of non-repeating tracks in the template
165            let first_repetition_and_non_repeating_tracks_used_space = non_repeating_track_used_space
166                + per_repetition_track_used_space
167                + ((non_auto_repeating_track_count as u32 + repetition_track_count as u32).saturating_sub(1) as f32
168                    * gap_size);
169
170            // If a single repetition already overflows the container then we return 1 as the repetition count
171            // (the number of repetitions is floored at 1)
172            if first_repetition_and_non_repeating_tracks_used_space > inner_container_size {
173                1u32
174            } else {
175                let per_repetition_gap_used_space = (repetition_track_count as f32) * gap_size;
176                let per_repetition_used_space = per_repetition_track_used_space + per_repetition_gap_used_space;
177                let num_repetition_that_fit = (inner_container_size
178                    - first_repetition_and_non_repeating_tracks_used_space)
179                    / per_repetition_used_space;
180
181                // If the container size is a preferred or maximum size:
182                //   Then we return the maximum number of repetitions that fit into the container without overflowing.
183                // If the container size is a minimum size:
184                //   - Then we return the minimum number of repetitions required to overflow the size.
185                //
186                // In all cases we add the additional repetition that was already accounted for in the special-case computation above
187                // Note: as-casts from f32 to u32 saturate, so a huge or infinite number of repetitions
188                // (e.g. due to zero-sized tracks) resolves to u32::MAX and is clamped below
189                match auto_fit_strategy {
190                    AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow => {
191                        (floor(num_repetition_that_fit) as u32).saturating_add(1)
192                    }
193                    AutoRepeatStrategy::MinRepetitionsThatDoOverflow => {
194                        (ceil(num_repetition_that_fit) as u32).saturating_add(1)
195                    }
196                }
197            }
198        }
199    };
200
201    // Clamp the number of repetitions based on the auto-repeat's insertion point so that tracks
202    // before it are preserved and tracks after it are truncated in source order.
203    // https://www.w3.org/TR/css-grid-1/#overlarge-grids
204    let remaining_tracks = (MAX_GRID_TRACKS as u32).saturating_sub(auto_repeat_insertion_point);
205    let num_repetitions = if remaining_tracks == 0 {
206        0
207    } else {
208        let max_repetitions = (remaining_tracks + repetition_track_count as u32 - 1) / repetition_track_count as u32;
209        num_repetitions.clamp(1, max_repetitions) as u16
210    };
211
212    let grid_template_track_count = min(
213        non_auto_repeating_track_count as u32 + (repetition_track_count as u32 * num_repetitions as u32),
214        MAX_GRID_TRACKS as u32,
215    ) as u16;
216    (num_repetitions, grid_template_track_count)
217}
218
219/// Resolve the track sizing functions of explicit tracks, automatically created tracks, and gutters
220/// given a set of track counts and all of the relevant styles
221pub(super) fn initialize_grid_tracks(
222    tracks: &mut Vec<GridTrack>,
223    counts: TrackCounts,
224    style: &impl GridContainerStyle,
225    axis: AbsoluteAxis,
226    auto_repetition_count: u16,
227    track_has_items: impl Fn(usize) -> bool,
228) {
229    // Extract styles
230    let track_template;
231    let auto_tracks;
232    let gap;
233    match axis {
234        AbsoluteAxis::Horizontal => {
235            track_template = style.grid_template_columns();
236            auto_tracks = style.grid_auto_columns();
237            gap = style.gap().width;
238        }
239        AbsoluteAxis::Vertical => {
240            track_template = style.grid_template_rows();
241            auto_tracks = style.grid_auto_rows();
242            gap = style.gap().height;
243        }
244    };
245
246    // Clear vector (in case this is a re-layout), reserve space for all tracks ahead of time to reduce allocations,
247    // and push the initial gutter
248    tracks.clear();
249    tracks.reserve((counts.len() * 2) + 1);
250    tracks.push(GridTrack::gutter(gap));
251
252    let auto_track_count = auto_tracks.len();
253
254    // Create negative implicit tracks
255    if counts.negative_implicit > 0 {
256        if auto_track_count == 0 {
257            let iter = core::iter::repeat(TrackSizingFunction::AUTO);
258            create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
259        } else {
260            let offset = auto_track_count - (counts.negative_implicit as usize % auto_track_count);
261            let iter = auto_tracks.clone().cycle().skip(offset);
262            create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
263        }
264    }
265
266    let mut current_track_index = (counts.negative_implicit) as usize;
267
268    // The maximum index up to which explicit tracks may be created. The explicit track count is clamped
269    // to MAX_GRID_TRACKS, so track creation from the template must be truncated to match.
270    let explicit_track_limit = (counts.negative_implicit + counts.explicit) as usize;
271
272    // Create explicit tracks
273    // An explicit check against the count (rather than just relying on track_template being empty) is required here
274    // because a count of zero can result from the track_template being invalid, in which case it should be ignored.
275    if counts.explicit > 0 {
276        if let Some(track_template) = track_template {
277            track_template.for_each(|track_sizing_function| {
278                match track_sizing_function {
279                    GenericGridTemplateComponent::Single(sizing_function) => {
280                        if current_track_index < explicit_track_limit {
281                            tracks.push(GridTrack::new(
282                                sizing_function.min_sizing_function(),
283                                sizing_function.max_sizing_function(),
284                            ));
285                            tracks.push(GridTrack::gutter(gap));
286                            current_track_index += 1;
287                        }
288                    }
289                    GenericGridTemplateComponent::Repeat(repeat) => match repeat.count() {
290                        RepetitionCount::Count(count) => {
291                            let repeated_track_count = (repeat.track_count() as usize * count as usize)
292                                .min(explicit_track_limit - current_track_index);
293                            let track_iter = repeat.tracks();
294                            let track_iter = track_iter.cycle().take(repeated_track_count);
295                            track_iter.for_each(|sizing_function| {
296                                tracks.push(GridTrack::new(
297                                    sizing_function.min_sizing_function(),
298                                    sizing_function.max_sizing_function(),
299                                ));
300                                tracks.push(GridTrack::gutter(gap));
301                                current_track_index += 1;
302                            });
303                        }
304                        RepetitionCount::AutoFit | RepetitionCount::AutoFill => {
305                            let auto_repeated_track_count = (repeat.track_count() as usize
306                                * auto_repetition_count as usize)
307                                .min(explicit_track_limit - current_track_index);
308                            let iter = repeat.tracks().cycle();
309                            for track_def in iter.take(auto_repeated_track_count) {
310                                let mut track =
311                                    GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function());
312                                let mut gutter = GridTrack::gutter(gap);
313
314                                // Auto-fit tracks that don't contain should be collapsed.
315                                if repeat.count() == RepetitionCount::AutoFit && !track_has_items(current_track_index) {
316                                    track.collapse();
317                                    gutter.collapse();
318                                }
319
320                                tracks.push(track);
321                                tracks.push(gutter);
322
323                                current_track_index += 1;
324                            }
325
326                            // Is this auto-repeat at the very end of the track list?
327                            let is_last = current_track_index == counts.len();
328
329                            // When collapsing auto-fit tracks (in the loop just above), we collapse the gutter after collapsed
330                            // tracks but not the gutter before. This is correct so long as there is another non-collapsed track
331                            // somewhere in the track list after the collapsed track.
332                            //
333                            // However, if the auto-fit repeat is at the very end of the track list, then there will be no such track.
334                            // In this case we need to iterate backwards through the track list until we find a non-collapsed track and
335                            // collapse the gutter just after that track.
336                            //
337                            // As all of the other tracks and gutters we find along the way will already be collapsed, we simplify the
338                            // implementation to collapsing all tracks and gutters until we find the first non-collapsed track.
339                            if repeat.count() == RepetitionCount::AutoFit && is_last {
340                                for previous_track in tracks.iter_mut().rev() {
341                                    if previous_track.kind == GridTrackKind::Track && !previous_track.is_collapsed {
342                                        break;
343                                    }
344                                    previous_track.collapse();
345                                }
346                            }
347                        }
348                    },
349                }
350            });
351        }
352    }
353
354    let grid_area_tracks = (counts.negative_implicit + counts.explicit) - current_track_index as u16;
355
356    // Create positive implicit tracks
357    if auto_track_count == 0 {
358        let iter = core::iter::repeat(TrackSizingFunction::AUTO);
359        create_implicit_tracks(tracks, counts.positive_implicit + grid_area_tracks, iter, gap)
360    } else {
361        let iter = auto_tracks.clone().cycle();
362        create_implicit_tracks(tracks, counts.positive_implicit + grid_area_tracks, iter, gap)
363    }
364
365    // Mark first and last grid lines as collapsed
366    tracks.first_mut().unwrap().collapse();
367    tracks.last_mut().unwrap().collapse();
368}
369
370/// Utility function for repeating logic of creating implicit tracks
371fn create_implicit_tracks(
372    tracks: &mut Vec<GridTrack>,
373    count: u16,
374    mut auto_tracks_iter: impl Iterator<Item = TrackSizingFunction>,
375    gap: LengthPercentage,
376) {
377    for _ in 0..count {
378        let track_def = auto_tracks_iter.next().unwrap();
379        tracks.push(GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function()));
380        tracks.push(GridTrack::gutter(gap));
381    }
382}
383
384#[cfg(test)]
385mod test {
386    use super::compute_explicit_grid_size_in_axis;
387    use super::initialize_grid_tracks;
388    use crate::compute::grid::explicit_grid::AutoRepeatStrategy;
389    use crate::compute::grid::types::GridTrackKind;
390    use crate::compute::grid::types::TrackCounts;
391    use crate::compute::grid::util::*;
392    use crate::geometry::AbsoluteAxis;
393    use crate::prelude::*;
394    use crate::sys::DefaultCheapStr;
395
396    #[test]
397    fn explicit_grid_sizing_no_repeats() {
398        let grid_style = (600.0, 600.0, 2, 4).into_grid();
399        let preferred_size = grid_style.size.map(|s| s.into_option());
400        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
401            &grid_style,
402            preferred_size.get_abs(AbsoluteAxis::Horizontal),
403            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
404            |_, _| 42.42,
405            AbsoluteAxis::Horizontal,
406        );
407        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
408            &grid_style,
409            preferred_size.get_abs(AbsoluteAxis::Vertical),
410            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
411            |_, _| 42.42,
412            AbsoluteAxis::Vertical,
413        );
414        assert_eq!(col_count, 2);
415        assert_eq!(row_count, 4);
416        assert_eq!(auto_col_reps, 0);
417        assert_eq!(auto_row_reps, 0);
418    }
419
420    #[test]
421    fn explicit_grid_sizing_auto_fill_exact_fit() {
422        use RepetitionCount::AutoFill;
423        let grid_style: Style<DefaultCheapStr> = Style {
424            display: Display::Grid,
425            size: Size { width: length(120.0), height: length(80.0) },
426            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
427            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
428            ..Default::default()
429        };
430        let preferred_size = grid_style.size.map(|s| s.into_option());
431        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
432            &grid_style,
433            preferred_size.get_abs(AbsoluteAxis::Horizontal),
434            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
435            |_, _| 42.42,
436            AbsoluteAxis::Horizontal,
437        );
438        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
439            &grid_style,
440            preferred_size.get_abs(AbsoluteAxis::Vertical),
441            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
442            |_, _| 42.42,
443            AbsoluteAxis::Vertical,
444        );
445        assert_eq!(col_count, 3);
446        assert_eq!(row_count, 4);
447        assert_eq!(auto_col_reps, 3);
448        assert_eq!(auto_row_reps, 4);
449    }
450
451    #[test]
452    fn explicit_grid_sizing_auto_fill_non_exact_fit() {
453        use RepetitionCount::AutoFill;
454        let grid_style: Style<DefaultCheapStr> = Style {
455            display: Display::Grid,
456            size: Size { width: length(140.0), height: length(90.0) },
457            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
458            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
459            ..Default::default()
460        };
461        let preferred_size = grid_style.size.map(|s| s.into_option());
462        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
463            &grid_style,
464            preferred_size.get_abs(AbsoluteAxis::Horizontal),
465            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
466            |_, _| 42.42,
467            AbsoluteAxis::Horizontal,
468        );
469        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
470            &grid_style,
471            preferred_size.get_abs(AbsoluteAxis::Vertical),
472            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
473            |_, _| 42.42,
474            AbsoluteAxis::Vertical,
475        );
476        assert_eq!(col_count, 3);
477        assert_eq!(row_count, 4);
478        assert_eq!(auto_col_reps, 3);
479        assert_eq!(auto_row_reps, 4);
480    }
481
482    #[test]
483    fn explicit_grid_sizing_auto_fill_min_size_exact_fit() {
484        use RepetitionCount::AutoFill;
485        let grid_style: Style<DefaultCheapStr> = Style {
486            display: Display::Grid,
487            min_size: Size { width: length(120.0), height: length(80.0) },
488            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
489            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
490            ..Default::default()
491        };
492        let inner_container_size = Size { width: Some(120.0), height: Some(80.0) };
493        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
494            &grid_style,
495            inner_container_size.get_abs(AbsoluteAxis::Horizontal),
496            AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
497            |_, _| 42.42,
498            AbsoluteAxis::Horizontal,
499        );
500        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
501            &grid_style,
502            inner_container_size.get_abs(AbsoluteAxis::Vertical),
503            AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
504            |_, _| 42.42,
505            AbsoluteAxis::Vertical,
506        );
507        assert_eq!(col_count, 3);
508        assert_eq!(row_count, 4);
509        assert_eq!(auto_col_reps, 3);
510        assert_eq!(auto_row_reps, 4);
511    }
512
513    #[test]
514    fn explicit_grid_sizing_auto_fill_min_size_non_exact_fit() {
515        use RepetitionCount::AutoFill;
516        let grid_style: Style<DefaultCheapStr> = Style {
517            display: Display::Grid,
518            min_size: Size { width: length(140.0), height: length(90.0) },
519            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
520            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
521            ..Default::default()
522        };
523        let inner_container_size = Size { width: Some(140.0), height: Some(90.0) };
524        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
525            &grid_style,
526            inner_container_size.get_abs(AbsoluteAxis::Horizontal),
527            AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
528            |_, _| 42.42,
529            AbsoluteAxis::Horizontal,
530        );
531        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
532            &grid_style,
533            inner_container_size.get_abs(AbsoluteAxis::Vertical),
534            AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
535            |_, _| 42.42,
536            AbsoluteAxis::Vertical,
537        );
538        assert_eq!(col_count, 4);
539        assert_eq!(row_count, 5);
540        assert_eq!(auto_col_reps, 4);
541        assert_eq!(auto_row_reps, 5);
542    }
543
544    #[test]
545    fn explicit_grid_sizing_auto_fill_multiple_repeated_tracks() {
546        use RepetitionCount::AutoFill;
547        let grid_style: Style<DefaultCheapStr> = Style {
548            display: Display::Grid,
549            size: Size { width: length(140.0), height: length(100.0) },
550            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0), length(20.0)])],
551            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0), length(10.0)])],
552            ..Default::default()
553        };
554        let preferred_size = grid_style.size.map(|s| s.into_option());
555        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
556            &grid_style,
557            preferred_size.get_abs(AbsoluteAxis::Horizontal),
558            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
559            |_, _| 42.42,
560            AbsoluteAxis::Horizontal,
561        );
562        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
563            &grid_style,
564            preferred_size.get_abs(AbsoluteAxis::Vertical),
565            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
566            |_, _| 42.42,
567            AbsoluteAxis::Vertical,
568        );
569        assert_eq!(col_count, 4); // 2 repetitions * 2 repeated tracks = 4 tracks in total
570        assert_eq!(row_count, 6); // 3 repetitions * 2 repeated tracks = 4 tracks in total
571        assert_eq!(auto_col_reps, 2);
572        assert_eq!(auto_row_reps, 3);
573    }
574
575    #[test]
576    fn explicit_grid_sizing_auto_fill_gap() {
577        use RepetitionCount::AutoFill;
578        let grid_style: Style<DefaultCheapStr> = Style {
579            display: Display::Grid,
580            size: Size { width: length(140.0), height: length(100.0) },
581            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
582            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
583            gap: length(20.0),
584            ..Default::default()
585        };
586        let preferred_size = grid_style.size.map(|s| s.into_option());
587        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
588            &grid_style,
589            preferred_size.get_abs(AbsoluteAxis::Horizontal),
590            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
591            |_, _| 42.42,
592            AbsoluteAxis::Horizontal,
593        );
594        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
595            &grid_style,
596            preferred_size.get_abs(AbsoluteAxis::Vertical),
597            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
598            |_, _| 42.42,
599            AbsoluteAxis::Vertical,
600        );
601        assert_eq!(col_count, 2); // 2 tracks + 1 gap
602        assert_eq!(row_count, 3); // 3 tracks + 2 gaps
603        assert_eq!(auto_col_reps, 2);
604        assert_eq!(auto_row_reps, 3);
605    }
606
607    #[test]
608    fn explicit_grid_sizing_no_defined_size() {
609        use RepetitionCount::AutoFill;
610        let grid_style: Style<DefaultCheapStr> = Style {
611            display: Display::Grid,
612            grid_template_columns: vec![repeat(AutoFill, vec![length(40.0), percent(0.5), length(20.0)])],
613            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
614            gap: length(20.0),
615            ..Default::default()
616        };
617        let preferred_size = grid_style.size.map(|s| s.into_option());
618        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
619            &grid_style,
620            preferred_size.get_abs(AbsoluteAxis::Horizontal),
621            AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
622            |_, _| 42.42,
623            AbsoluteAxis::Horizontal,
624        );
625        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
626            &grid_style,
627            preferred_size.get_abs(AbsoluteAxis::Vertical),
628            AutoRepeatStrategy::MinRepetitionsThatDoOverflow,
629            |_, _| 42.42,
630            AbsoluteAxis::Vertical,
631        );
632        assert_eq!(col_count, 3);
633        assert_eq!(row_count, 1);
634        assert_eq!(auto_col_reps, 1);
635        assert_eq!(auto_row_reps, 1);
636    }
637
638    #[test]
639    fn explicit_grid_sizing_mix_repeated_and_non_repeated() {
640        use RepetitionCount::AutoFill;
641        let grid_style: Style<DefaultCheapStr> = Style {
642            display: Display::Grid,
643            size: Size { width: length(140.0), height: length(100.0) },
644            grid_template_columns: vec![length(20.0), repeat(AutoFill, vec![length(40.0)])],
645            grid_template_rows: vec![length(40.0), repeat(AutoFill, vec![length(20.0)])],
646            gap: length(20.0),
647            ..Default::default()
648        };
649        let preferred_size = grid_style.size.map(|s| s.into_option());
650        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
651            &grid_style,
652            preferred_size.get_abs(AbsoluteAxis::Horizontal),
653            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
654            |_, _| 42.42,
655            AbsoluteAxis::Horizontal,
656        );
657        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
658            &grid_style,
659            preferred_size.get_abs(AbsoluteAxis::Vertical),
660            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
661            |_, _| 42.42,
662            AbsoluteAxis::Vertical,
663        );
664        assert_eq!(col_count, 3); // 3 tracks + 2 gaps
665        assert_eq!(row_count, 2); // 2 tracks + 1 gap
666        assert_eq!(auto_col_reps, 2);
667        assert_eq!(auto_row_reps, 1);
668    }
669
670    #[test]
671    fn explicit_grid_sizing_mix_with_padding() {
672        use RepetitionCount::AutoFill;
673        let grid_style: Style<DefaultCheapStr> = Style {
674            display: Display::Grid,
675            size: Size { width: length(120.0), height: length(120.0) },
676            padding: Rect { left: length(10.0), right: length(10.0), top: length(20.0), bottom: length(20.0) },
677            grid_template_columns: vec![repeat(AutoFill, vec![length(20.0)])],
678            grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
679            ..Default::default()
680        };
681        let inner_container_size = Size { width: Some(100.0), height: Some(80.0) };
682        let (auto_col_reps, col_count) = compute_explicit_grid_size_in_axis(
683            &grid_style,
684            inner_container_size.get_abs(AbsoluteAxis::Horizontal),
685            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
686            |_, _| 42.42,
687            AbsoluteAxis::Horizontal,
688        );
689        let (auto_row_reps, row_count) = compute_explicit_grid_size_in_axis(
690            &grid_style,
691            inner_container_size.get_abs(AbsoluteAxis::Vertical),
692            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
693            |_, _| 42.42,
694            AbsoluteAxis::Vertical,
695        );
696        assert_eq!(col_count, 5); // 40px horizontal padding
697        assert_eq!(row_count, 4); // 20px vertical padding
698        assert_eq!(auto_col_reps, 5);
699        assert_eq!(auto_row_reps, 4);
700    }
701
702    #[test]
703    fn auto_repeat_clamping_preserves_template_source_order() {
704        use RepetitionCount::{AutoFill, Count};
705
706        let auto_repeat_first: Style<DefaultCheapStr> = Style {
707            display: Display::Grid,
708            grid_template_columns: vec![repeat(AutoFill, vec![length(2.0)]), repeat(Count(10_000), vec![length(37.0)])],
709            ..Default::default()
710        };
711        let (repetitions, track_count) = compute_explicit_grid_size_in_axis(
712            &auto_repeat_first,
713            None,
714            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
715            |_, _| 42.42,
716            AbsoluteAxis::Horizontal,
717        );
718        assert_eq!((repetitions, track_count), (1, 10_000));
719        let mut tracks = Vec::new();
720        initialize_grid_tracks(
721            &mut tracks,
722            TrackCounts::from_raw(0, track_count, 0),
723            &auto_repeat_first,
724            AbsoluteAxis::Horizontal,
725            repetitions,
726            |_| false,
727        );
728        assert_eq!(tracks[1].min_track_sizing_function, MinTrackSizingFunction::from_length(2.0));
729        assert_eq!(tracks[3].min_track_sizing_function, MinTrackSizingFunction::from_length(37.0));
730
731        let auto_repeat_last: Style<DefaultCheapStr> = Style {
732            display: Display::Grid,
733            grid_template_columns: vec![repeat(Count(10_000), vec![length(37.0)]), repeat(AutoFill, vec![length(2.0)])],
734            ..Default::default()
735        };
736        let (repetitions, track_count) = compute_explicit_grid_size_in_axis(
737            &auto_repeat_last,
738            None,
739            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
740            |_, _| 42.42,
741            AbsoluteAxis::Horizontal,
742        );
743        assert_eq!((repetitions, track_count), (0, 10_000));
744    }
745
746    #[test]
747    fn explicit_track_count_accumulation_saturates() {
748        use RepetitionCount::{AutoFill, Count};
749
750        let repeated_tracks = vec![length(1.0); 32_769];
751        let grid_style: Style<DefaultCheapStr> = Style {
752            display: Display::Grid,
753            grid_template_columns: vec![
754                repeat(Count(u16::MAX), repeated_tracks.clone()),
755                repeat(Count(u16::MAX), repeated_tracks),
756                repeat(AutoFill, vec![length(1.0)]),
757            ],
758            ..Default::default()
759        };
760        let result = compute_explicit_grid_size_in_axis(
761            &grid_style,
762            None,
763            AutoRepeatStrategy::MaxRepetitionsThatDoNotOverflow,
764            |_, _| 42.42,
765            AbsoluteAxis::Horizontal,
766        );
767        assert_eq!(result, (0, 10_000));
768    }
769
770    #[test]
771    fn test_initialize_grid_tracks() {
772        let minpx0 = MinTrackSizingFunction::from_length(0.0);
773        let minpx20 = MinTrackSizingFunction::from_length(20.0);
774        let minpx100 = MinTrackSizingFunction::from_length(100.0);
775
776        let maxpx0 = MaxTrackSizingFunction::from_length(0.0);
777        let maxpx20 = MaxTrackSizingFunction::from_length(20.0);
778        let maxpx100 = MaxTrackSizingFunction::from_length(100.0);
779
780        // Setup test
781        let grid_style: Style<DefaultCheapStr> = Style {
782            display: Display::Grid,
783            gap: length(20.0),
784            grid_template_columns: vec![length(100.0), minmax(length(100.0), fr(2.0)), fr(1.0)],
785            grid_auto_columns: vec![auto(), length(100.0)],
786            ..Default::default()
787        };
788        let track_counts = TrackCounts {
789            negative_implicit: 3,
790            explicit: grid_style.grid_template_columns.len() as u16,
791            positive_implicit: 3,
792        };
793
794        // Call function
795        let mut tracks = Vec::new();
796        initialize_grid_tracks(&mut tracks, track_counts, &grid_style, AbsoluteAxis::Horizontal, 0, |_| false);
797
798        // Assertions
799        let expected = vec![
800            // Gutter
801            (GridTrackKind::Gutter, minpx0, maxpx0),
802            // Negative implicit tracks
803            (GridTrackKind::Track, minpx100, maxpx100),
804            (GridTrackKind::Gutter, minpx20, maxpx20),
805            (GridTrackKind::Track, auto(), auto()),
806            (GridTrackKind::Gutter, minpx20, maxpx20),
807            (GridTrackKind::Track, minpx100, maxpx100),
808            (GridTrackKind::Gutter, minpx20, maxpx20),
809            // Explicit tracks
810            (GridTrackKind::Track, minpx100, maxpx100),
811            (GridTrackKind::Gutter, minpx20, maxpx20),
812            (GridTrackKind::Track, minpx100, MaxTrackSizingFunction::from_fr(2.0)), // Note: separate min-max functions
813            (GridTrackKind::Gutter, minpx20, maxpx20),
814            (GridTrackKind::Track, auto(), MaxTrackSizingFunction::from_fr(1.0)), // Note: min sizing function of flex sizing functions is AUTO
815            (GridTrackKind::Gutter, minpx20, maxpx20),
816            // Positive implicit tracks
817            (GridTrackKind::Track, auto(), auto()),
818            (GridTrackKind::Gutter, minpx20, maxpx20),
819            (GridTrackKind::Track, minpx100, maxpx100),
820            (GridTrackKind::Gutter, minpx20, maxpx20),
821            (GridTrackKind::Track, auto(), auto()),
822            (GridTrackKind::Gutter, minpx0, maxpx0),
823        ];
824
825        assert_eq!(tracks.len(), expected.len(), "Number of tracks doesn't match");
826
827        for (idx, (actual, (kind, min, max))) in tracks.into_iter().zip(expected).enumerate() {
828            assert_eq!(actual.kind, kind, "Track {idx} (0-based index)");
829            assert_eq!(actual.min_track_sizing_function, min, "Track {idx} (0-based index)");
830            assert_eq!(actual.max_track_sizing_function, max, "Track {idx} (0-based index)");
831        }
832    }
833}