taffy/compute/grid/
implicit_grid.rs

1//! This module is not required for spec compliance, but is used as a performance optimisation
2//! to reduce the number of allocations required when creating a grid.
3use crate::geometry::Line;
4use crate::style::{GenericGridPlacement, GridPlacement, Style};
5use core::cmp::{max, min};
6
7use super::types::TrackCounts;
8use super::OriginZeroLine;
9
10/// Estimate the number of rows and columns in the grid
11/// This is used as a performance optimisation to pre-size vectors and reduce allocations. It also forms a necessary step
12/// in the auto-placement
13///   - The estimates for the explicit and negative implicit track counts are exact.
14///   - However, the estimates for the positive explicit track count is a lower bound as auto-placement can affect this
15///     in ways which are impossible to predict until the auto-placement algorithm is run.
16///
17/// Note that this function internally mixes use of grid track numbers and grid line numbers
18pub(crate) fn compute_grid_size_estimate<'a>(
19    explicit_col_count: u16,
20    explicit_row_count: u16,
21    child_styles_iter: impl Iterator<Item = &'a Style>,
22) -> (TrackCounts, TrackCounts) {
23    // Iterate over children, producing an estimate of the min and max grid lines (in origin-zero coordinates where)
24    // along with the span of each item
25    let (col_min, col_max, col_max_span, row_min, row_max, row_max_span) =
26        get_known_child_positions(child_styles_iter, explicit_col_count, explicit_row_count);
27
28    // Compute *track* count estimates for each axis from:
29    //   - The explicit track counts
30    //   - The origin-zero coordinate min and max grid line variables
31    let negative_implicit_inline_tracks = col_min.implied_negative_implicit_tracks();
32    let explicit_inline_tracks = explicit_col_count;
33    let mut positive_implicit_inline_tracks = col_max.implied_positive_implicit_tracks(explicit_col_count);
34    let negative_implicit_block_tracks = row_min.implied_negative_implicit_tracks();
35    let explicit_block_tracks = explicit_row_count;
36    let mut positive_implicit_block_tracks = row_max.implied_positive_implicit_tracks(explicit_row_count);
37
38    // In each axis, adjust positive track estimate if any items have a span that does not fit within
39    // the total number of tracks in the estimate
40    let tot_inline_tracks = negative_implicit_inline_tracks + explicit_inline_tracks + positive_implicit_inline_tracks;
41    if tot_inline_tracks < col_max_span {
42        positive_implicit_inline_tracks = col_max_span - explicit_inline_tracks - negative_implicit_inline_tracks;
43    }
44
45    let tot_block_tracks = negative_implicit_block_tracks + explicit_block_tracks + positive_implicit_block_tracks;
46    if tot_block_tracks < row_max_span {
47        positive_implicit_block_tracks = row_max_span - explicit_block_tracks - negative_implicit_block_tracks;
48    }
49
50    let column_counts =
51        TrackCounts::from_raw(negative_implicit_inline_tracks, explicit_inline_tracks, positive_implicit_inline_tracks);
52
53    let row_counts =
54        TrackCounts::from_raw(negative_implicit_block_tracks, explicit_block_tracks, positive_implicit_block_tracks);
55
56    (column_counts, row_counts)
57}
58
59/// Iterate over children, producing an estimate of the min and max grid *lines* along with the span of each item
60///
61/// Min and max grid lines are returned in origin-zero coordinates)
62/// The span is measured in tracks spanned
63fn get_known_child_positions<'a>(
64    children_iter: impl Iterator<Item = &'a Style>,
65    explicit_col_count: u16,
66    explicit_row_count: u16,
67) -> (OriginZeroLine, OriginZeroLine, u16, OriginZeroLine, OriginZeroLine, u16) {
68    let (mut col_min, mut col_max, mut col_max_span) = (OriginZeroLine(0), OriginZeroLine(0), 0);
69    let (mut row_min, mut row_max, mut row_max_span) = (OriginZeroLine(0), OriginZeroLine(0), 0);
70    children_iter.for_each(|child_style: &Style| {
71        // Note: that the children reference the lines in between (and around) the tracks not tracks themselves,
72        // and thus we must subtract 1 to get an accurate estimate of the number of tracks
73        let (child_col_min, child_col_max, child_col_span) =
74            child_min_line_max_line_span(child_style.grid_column, explicit_col_count);
75        let (child_row_min, child_row_max, child_row_span) =
76            child_min_line_max_line_span(child_style.grid_row, explicit_row_count);
77        col_min = min(col_min, child_col_min);
78        col_max = max(col_max, child_col_max);
79        col_max_span = max(col_max_span, child_col_span);
80        row_min = min(row_min, child_row_min);
81        row_max = max(row_max, child_row_max);
82        row_max_span = max(row_max_span, child_row_span);
83    });
84
85    (col_min, col_max, col_max_span, row_min, row_max, row_max_span)
86}
87
88/// Helper function for `compute_grid_size_estimate`
89/// Produces a conservative estimate of the greatest and smallest grid lines used by a single grid item
90///
91/// Values are returned in origin-zero coordinates
92#[inline]
93fn child_min_line_max_line_span(
94    line: Line<GridPlacement>,
95    explicit_track_count: u16,
96) -> (OriginZeroLine, OriginZeroLine, u16) {
97    use GenericGridPlacement::*;
98
99    // 8.3.1. Grid Placement Conflict Handling
100    // A. If the placement for a grid item contains two lines, and the start line is further end-ward than the end line, swap the two lines.
101    // B. If the start line is equal to the end line, remove the end line.
102    // C. If the placement contains two spans, remove the one contributed by the end grid-placement property.
103    // D. If the placement contains only a span for a named line, replace it with a span of 1.
104
105    // Convert line into origin-zero coordinates before attempting to analyze
106    let oz_line = line.into_origin_zero(explicit_track_count);
107
108    let min = match (oz_line.start, oz_line.end) {
109        // Both tracks specified
110        (Line(track1), Line(track2)) => {
111            // See rules A and B above
112            if track1 == track2 {
113                track1
114            } else {
115                min(track1, track2)
116            }
117        }
118
119        // Start track specified
120        (Line(track), Auto) => track,
121        (Line(track), Span(_)) => track,
122
123        // End track specified
124        (Auto, Line(track)) => track,
125        (Span(span), Line(track)) => track - span,
126
127        // Only spans or autos
128        // We ignore spans here by returning 0 which never effect the estimate as these are accounted for separately
129        (Auto | Span(_), Auto | Span(_)) => OriginZeroLine(0),
130    };
131
132    let max = match (oz_line.start, oz_line.end) {
133        // Both tracks specified
134        (Line(track1), Line(track2)) => {
135            // See rules A and B above
136            if track1 == track2 {
137                track1 + 1
138            } else {
139                max(track1, track2)
140            }
141        }
142
143        // Start track specified
144        (Line(track), Auto) => track + 1,
145        (Line(track), Span(span)) => track + span,
146
147        // End track specified
148        (Auto, Line(track)) => track,
149        (Span(_), Line(track)) => track,
150
151        // Only spans or autos
152        // We ignore spans here by returning 0 which never effect the estimate as these are accounted for separately
153        (Auto | Span(_), Auto | Span(_)) => OriginZeroLine(0),
154    };
155
156    // Calculate span only for indefinitely placed items as we don't need for other items (whose required space will
157    // be taken into account by min and max)
158    let span = match (line.start, line.end) {
159        (Auto | Span(_), Auto | Span(_)) => line.indefinite_span(),
160        _ => 1,
161    };
162
163    (min, max, span)
164}
165
166#[allow(clippy::bool_assert_comparison)]
167#[cfg(test)]
168mod tests {
169    mod test_child_min_max_line {
170        use super::super::child_min_line_max_line_span;
171        use super::super::OriginZeroLine;
172        use crate::geometry::Line;
173        use crate::style_helpers::*;
174
175        #[test]
176        fn child_min_max_line_auto() {
177            let (min_col, max_col, span) = child_min_line_max_line_span(Line { start: line(5), end: span(6) }, 6);
178            assert_eq!(min_col, OriginZeroLine(4));
179            assert_eq!(max_col, OriginZeroLine(10));
180            assert_eq!(span, 1);
181        }
182
183        #[test]
184        fn child_min_max_line_negative_track() {
185            let (min_col, max_col, span) = child_min_line_max_line_span(Line { start: line(-5), end: span(3) }, 6);
186            assert_eq!(min_col, OriginZeroLine(2));
187            assert_eq!(max_col, OriginZeroLine(5));
188            assert_eq!(span, 1);
189        }
190    }
191
192    mod test_intial_grid_sizing {
193        use super::super::compute_grid_size_estimate;
194        use crate::compute::grid::util::test_helpers::*;
195        use crate::style_helpers::*;
196
197        #[test]
198        fn explicit_grid_sizing_with_children() {
199            let explicit_col_count = 6;
200            let explicit_row_count = 8;
201            let child_styles = vec![
202                (line(1), span(2), line(2), auto()).into_grid_child(),
203                (line(-4), auto(), line(-2), auto()).into_grid_child(),
204            ];
205            let (inline, block) =
206                compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles.iter());
207            assert_eq!(inline.negative_implicit, 0);
208            assert_eq!(inline.explicit, explicit_col_count);
209            assert_eq!(inline.positive_implicit, 0);
210            assert_eq!(block.negative_implicit, 0);
211            assert_eq!(block.explicit, explicit_row_count);
212            assert_eq!(block.positive_implicit, 0);
213        }
214
215        #[test]
216        fn negative_implicit_grid_sizing() {
217            let explicit_col_count = 4;
218            let explicit_row_count = 4;
219            let child_styles = vec![
220                (line(-6), span(2), line(-8), auto()).into_grid_child(),
221                (line(4), auto(), line(3), auto()).into_grid_child(),
222            ];
223            let (inline, block) =
224                compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles.iter());
225            assert_eq!(inline.negative_implicit, 1);
226            assert_eq!(inline.explicit, explicit_col_count);
227            assert_eq!(inline.positive_implicit, 0);
228            assert_eq!(block.negative_implicit, 3);
229            assert_eq!(block.explicit, explicit_row_count);
230            assert_eq!(block.positive_implicit, 0);
231        }
232    }
233}