Skip to main content

taffy/compute/grid/types/
grid_item.rs

1//! Contains GridItem used to represent a single grid item during layout
2use super::GridTrack;
3use crate::compute::common::sizing_keyword::{resolve_sizing_keyword, SizingKeywordResolution};
4use crate::compute::grid::OriginZeroLine;
5use crate::geometry::AbstractAxis;
6use crate::geometry::{Line, Point, Rect, Size};
7use crate::style::{AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, Overflow};
8use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, OofCandidates, SizingMode};
9use crate::util::sys::f32_max;
10use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
11use crate::{AlignItemsKeyword, BoxSizing, GridItemStyle, LengthPercentage};
12use core::ops::Range;
13
14/// The known_dimensions computed for a grid item for a given grid area size
15#[derive(Debug, Clone, Copy)]
16pub(in super::super) struct KnownDimensionsCacheEntry {
17    /// The grid area size that `known_dimensions` was computed for
18    grid_area_size: Size<Option<f32>>,
19    /// The cached known_dimensions
20    known_dimensions: Size<Option<f32>>,
21}
22
23/// Represents a single grid item
24#[derive(Debug)]
25pub(in super::super) struct GridItem {
26    /// The id of the node that this item represents
27    pub node: NodeId,
28
29    /// The index of the item in the children array
30    pub source_order: u16,
31
32    /// The item's definite row-start and row-end, as resolved by the placement algorithm
33    /// (in origin-zero coordinates). Set by the placement algorithm.
34    pub row: Line<OriginZeroLine>,
35    /// The items definite column-start and column-end, as resolved by the placement algorithm
36    /// (in origin-zero coordinates). Set by the placement algorithm.
37    pub column: Line<OriginZeroLine>,
38
39    /// Is it a compressible replaced element?
40    /// https://drafts.csswg.org/css-sizing-3/#min-content-zero
41    pub is_compressible_replaced: bool,
42    /// Whether the item is a replaced element. Such items are not stretched by `normal` alignment.
43    pub is_replaced: bool,
44    /// The item's overflow style
45    pub overflow: Point<Overflow>,
46    /// The item's box_sizing style
47    pub box_sizing: BoxSizing,
48    /// The item's size style
49    pub size: Size<Dimension>,
50    /// The item's min_size style
51    pub min_size: Size<LengthPercentageAuto>,
52    /// The item's max_size style
53    pub max_size: Size<LengthPercentageAuto>,
54    /// The item's aspect_ratio style
55    pub aspect_ratio: Option<f32>,
56    /// The item's padding style
57    pub padding: Rect<LengthPercentage>,
58    /// The item's border style
59    pub border: Rect<LengthPercentage>,
60    /// The item's margin style
61    pub margin: Rect<LengthPercentageAuto>,
62    /// The item's align_self property, or the parent's align_items property if it is `None`
63    pub align_self: AlignSelf,
64    /// The item's justify_self property, or the parent's justify_items property if it is `None`
65    pub justify_self: AlignSelf,
66    /// The item's first baseline (horizontal), measured from the top of its border box. Set from the item's
67    /// intrinsic layout when resolving baseline alignment during track sizing, then from its final layout.
68    pub baseline: Option<f32>,
69    /// Shim for baseline alignment that acts like an extra top margin
70    /// TODO: Support last baseline and vertical text baselines
71    pub baseline_shim: f32,
72
73    /// The item's definite row-start and row-end (same as `row` field, except in a different coordinate system)
74    /// (as indexes into the Vec<GridTrack> stored in a grid's AbstractAxisTracks)
75    pub row_indexes: Line<u16>,
76    /// The items definite column-start and column-end (same as `column` field, except in a different coordinate system)
77    /// (as indexes into the Vec<GridTrack> stored in a grid's AbstractAxisTracks)
78    pub column_indexes: Line<u16>,
79
80    /// Whether the item crosses a flexible row
81    pub crosses_flexible_row: bool,
82    /// Whether the item crosses a flexible column
83    pub crosses_flexible_column: bool,
84    /// Whether the item crosses a intrinsic row
85    pub crosses_intrinsic_row: bool,
86    /// Whether the item crosses a intrinsic column
87    pub crosses_intrinsic_column: bool,
88
89    // Caches for intrinsic size computation. These caches are only valid for a single run of the track-sizing algorithm.
90    /// Cache for the known_dimensions input to intrinsic sizing computation
91    pub grid_area_size_cache: Option<Size<Option<f32>>>,
92    /// Cache for the known_dimensions passed to the child when computing its intrinsic contributions, stored along with
93    /// the grid area size it was computed for. Must be cleared whenever `baseline_shim` changes.
94    pub known_dimensions_cache: Option<KnownDimensionsCacheEntry>,
95    /// Cache for the min-content size
96    pub min_content_contribution_cache: Size<Option<f32>>,
97    /// Cache for the minimum contribution
98    pub minimum_contribution_cache: Size<Option<f32>>,
99    /// Cache for the max-content size
100    pub max_content_contribution_cache: Size<Option<f32>>,
101
102    /// Final y position, excluding any relative offset (which does not affect the container's baseline).
103    /// Used to compute baseline alignment for the container.
104    pub y_position: f32,
105    /// Final height. Used to compute baseline alignment for the container.
106    pub height: f32,
107
108    /// Out-of-flow candidates bubbled out of this item's subtree (anchors relative to the
109    /// container's border box)
110    pub oof_candidates: OofCandidates,
111}
112
113impl GridItem {
114    /// Create a new item from its style. Its placement (`row`/`column`) is filled in by the placement algorithm.
115    pub fn new_with_style_and_order<S: GridItemStyle>(
116        node: NodeId,
117        style: S,
118        parent_align_items: AlignItems,
119        parent_justify_items: AlignItems,
120        source_order: u16,
121    ) -> Self {
122        const UNPLACED: Line<OriginZeroLine> = Line { start: OriginZeroLine(0), end: OriginZeroLine(0) };
123        GridItem {
124            node,
125            source_order,
126            row: UNPLACED,
127            column: UNPLACED,
128            is_compressible_replaced: style.is_compressible_replaced(),
129            is_replaced: style.is_replaced(),
130            overflow: style.overflow(),
131            box_sizing: style.box_sizing(),
132            size: style.size(),
133            min_size: style.min_size(),
134            max_size: style.max_size(),
135            aspect_ratio: style.aspect_ratio(),
136            padding: style.padding(),
137            border: style.border(),
138            margin: style.margin(),
139            align_self: style.align_self().unwrap_or(parent_align_items),
140            justify_self: style.justify_self().unwrap_or(parent_justify_items),
141            baseline: None,
142            baseline_shim: 0.0,
143            row_indexes: Line { start: 0, end: 0 }, // Properly initialised later
144            column_indexes: Line { start: 0, end: 0 }, // Properly initialised later
145            crosses_flexible_row: false,            // Properly initialised later
146            crosses_flexible_column: false,         // Properly initialised later
147            crosses_intrinsic_row: false,           // Properly initialised later
148            crosses_intrinsic_column: false,        // Properly initialised later
149            grid_area_size_cache: None,
150            min_content_contribution_cache: Size::NONE,
151            max_content_contribution_cache: Size::NONE,
152            known_dimensions_cache: None,
153            minimum_contribution_cache: Size::NONE,
154            y_position: 0.0,
155            height: 0.0,
156            oof_candidates: OofCandidates::NONE,
157        }
158    }
159
160    /// Whether the item has an auto margin in the block axis
161    #[inline(always)]
162    pub fn has_auto_block_margin(&self) -> bool {
163        self.margin.top.is_auto() || self.margin.bottom.is_auto()
164    }
165
166    /// Whether the item's block size depends on the size of its row(s), creating a cyclic
167    /// dependency with baseline alignment (which affects row sizing). Per
168    /// <https://www.w3.org/TR/css-grid-1/#row-align> such items do not participate in baseline
169    /// alignment and are aligned using their fallback alignment instead.
170    #[inline(always)]
171    pub fn has_cyclic_block_size_dependency(&self) -> bool {
172        self.size.height.0.uses_percentage() && (self.crosses_intrinsic_row || self.crosses_flexible_row)
173    }
174
175    /// Returns true if the item participates in baseline alignment: it has `align-self: baseline`
176    /// and neither of its block-axis margins are `auto`.
177    /// See <https://www.w3.org/TR/css-align-3/#baseline-align-self>
178    #[inline(always)]
179    pub fn participates_in_baseline_alignment(&self) -> bool {
180        self.align_self.keyword == AlignItemsKeyword::Baseline
181            && !self.has_auto_block_margin()
182            && !self.has_cyclic_block_size_dependency()
183    }
184
185    /// This item's placement in the specified axis in OriginZero coordinates
186    pub fn placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine> {
187        match axis {
188            AbstractAxis::Block => self.row,
189            AbstractAxis::Inline => self.column,
190        }
191    }
192
193    /// This item's placement in the specified axis as GridTrackVec indices
194    pub fn placement_indexes(&self, axis: AbstractAxis) -> Line<u16> {
195        match axis {
196            AbstractAxis::Block => self.row_indexes,
197            AbstractAxis::Inline => self.column_indexes,
198        }
199    }
200
201    /// Returns a range which can be used as an index into the GridTrackVec in the specified axis
202    /// which will produce a sub-slice of covering all the tracks and lines that this item spans
203    /// excluding the lines that bound it.
204    pub fn track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize> {
205        let indexes = self.placement_indexes(axis);
206        (indexes.start as usize + 1)..(indexes.end as usize)
207    }
208
209    /// Whether any track spanned by this item in the specified axis satisfies `predicate`
210    pub fn spans_track_matching(
211        &self,
212        axis: AbstractAxis,
213        axis_tracks: &[GridTrack],
214        predicate: impl Fn(&GridTrack) -> bool,
215    ) -> bool {
216        axis_tracks[self.track_range_excluding_lines(axis)].iter().any(predicate)
217    }
218
219    /// Returns the number of tracks that this item spans in the specified axis
220    pub fn span(&self, axis: AbstractAxis) -> u16 {
221        match axis {
222            AbstractAxis::Block => self.row.span(),
223            AbstractAxis::Inline => self.column.span(),
224        }
225    }
226
227    /// Returns the pre-computed value indicating whether the grid item crosses a flexible track in
228    /// the specified axis
229    pub fn crosses_flexible_track(&self, axis: AbstractAxis) -> bool {
230        match axis {
231            AbstractAxis::Inline => self.crosses_flexible_column,
232            AbstractAxis::Block => self.crosses_flexible_row,
233        }
234    }
235
236    /// Returns the pre-computed value indicating whether the grid item crosses an intrinsic track in
237    /// the specified axis
238    pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
239        match axis {
240            AbstractAxis::Inline => self.crosses_intrinsic_column,
241            AbstractAxis::Block => self.crosses_intrinsic_row,
242        }
243    }
244
245    /// For an item spanning multiple tracks, the upper limit used to calculate its limited min-/max-content contribution is the
246    /// sum of the fixed max track sizing functions of any tracks it spans, and is applied if it only spans such tracks.
247    pub fn spanned_track_limit(
248        &mut self,
249        axis: AbstractAxis,
250        axis_tracks: &[GridTrack],
251        axis_parent_size: Option<f32>,
252        resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
253    ) -> Option<f32> {
254        let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
255        let tracks_all_fixed = spanned_tracks.iter().all(|track| {
256            track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).is_some()
257        });
258        if tracks_all_fixed {
259            let limit: f32 = spanned_tracks
260                .iter()
261                .map(|track| {
262                    track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).unwrap()
263                })
264                .sum();
265            Some(limit)
266        } else {
267            None
268        }
269    }
270
271    /// Similar to the spanned_track_limit, but excludes FitContent arguments from the limit.
272    /// Used to clamp the automatic minimum contributions of an item
273    pub fn spanned_fixed_track_limit(
274        &mut self,
275        axis: AbstractAxis,
276        axis_tracks: &[GridTrack],
277        axis_parent_size: Option<f32>,
278        resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
279    ) -> Option<f32> {
280        let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
281        let tracks_all_fixed = spanned_tracks.iter().all(|track| {
282            track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).is_some()
283        });
284        if tracks_all_fixed {
285            let limit: f32 = spanned_tracks
286                .iter()
287                .map(|track| {
288                    track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).unwrap()
289                })
290                .sum();
291            Some(limit)
292        } else {
293            None
294        }
295    }
296
297    /// Retrieve the known_dimensions for the given grid area size from the cache or compute them.
298    /// The min-content and max-content contributions of an item are both computed from the same known_dimensions.
299    #[inline(always)]
300    fn known_dimensions_cached(
301        &mut self,
302        tree: &mut impl LayoutPartialTree,
303        grid_area_size: Size<Option<f32>>,
304    ) -> Size<Option<f32>> {
305        if let Some(entry) = self.known_dimensions_cache {
306            if entry.grid_area_size == grid_area_size {
307                return entry.known_dimensions;
308            }
309        }
310        let known_dimensions = self.known_dimensions(tree, grid_area_size);
311        self.known_dimensions_cache = Some(KnownDimensionsCacheEntry { grid_area_size, known_dimensions });
312        known_dimensions
313    }
314
315    /// Whether the given self-alignment stretches the item: `stretch`, or `normal` unless the item is replaced
316    #[inline(always)]
317    fn is_stretch_aligned(&self, alignment: AlignSelf) -> bool {
318        match alignment.keyword {
319            AlignItemsKeyword::Stretch => true,
320            AlignItemsKeyword::Normal => !self.is_replaced,
321            _ => false,
322        }
323    }
324
325    /// Compute the known_dimensions to be passed to the child sizing functions
326    /// The key thing that is being done here is applying stretch alignment, which is necessary to
327    /// allow percentage sizes further down the tree to resolve properly in some cases
328    fn known_dimensions(
329        &self,
330        tree: &mut impl LayoutPartialTree,
331        grid_area_size: Size<Option<f32>>,
332    ) -> Size<Option<f32>> {
333        let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
334
335        let aspect_ratio = self.aspect_ratio;
336        // CSS resolves percentage padding and border against the inline size of the containing
337        // block. For a grid item under intrinsic measurement, that inline-size basis is the grid
338        // area's width when it is definite.
339        // Spec:
340        // https://www.w3.org/TR/css-grid-1/#item-margins
341        // https://www.w3.org/TR/CSS22/box.html#padding-properties
342        let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
343        let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
344        let padding_border_size = (padding + border).sum_axes();
345        let box_sizing_adjustment =
346            if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
347        let inherent_size = self
348            .size
349            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
350            .maybe_apply_aspect_ratio(aspect_ratio)
351            .maybe_add(box_sizing_adjustment);
352        let min_size = self
353            .min_size
354            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
355            .maybe_apply_aspect_ratio(aspect_ratio)
356            .maybe_add(box_sizing_adjustment)
357            // The size of the item is floored by its padding and border
358            .or(padding_border_size.map(Some))
359            .maybe_max(padding_border_size);
360        let max_size = self
361            .max_size
362            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
363            .maybe_apply_aspect_ratio(aspect_ratio)
364            .maybe_add(box_sizing_adjustment);
365
366        let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins).maybe_max(Size::ZERO);
367
368        // If node is absolutely positioned and width is not set explicitly, then deduce it
369        // from left, right and container_content_box if both are set.
370        let width = inherent_size.width.or_else(|| {
371            // A width that is a sizing keyword is not auto, so it does not stretch. The stretch
372            // keyword resolves to an exact width; the others resolve during content measurement.
373            if self.size.width.is_sizing_keyword() {
374                return match resolve_sizing_keyword(
375                    self.size.width,
376                    grid_area_minus_item_margins_size.width,
377                    grid_area_size.width,
378                    |val, basis| tree.calc(val, basis),
379                ) {
380                    Some(SizingKeywordResolution::Exact(width)) => Some(width),
381                    _ => None,
382                };
383            }
384
385            // Apply width based on stretch alignment if:
386            //  - Alignment style is "stretch" or "normal". Note that "normal" is treated as "stretch"
387            //    here regardless of whether the item has a preferred size or aspect ratio in this axis,
388            //    which differs from the rule used when the item is finally aligned (see `align_and_position_item`).
389            //    Replaced elements are not stretched by "normal".
390            //  - The node is not absolutely positioned
391            //  - The node does not have auto margins in this axis.
392            if !self.margin.left.is_auto() && !self.margin.right.is_auto() && self.is_stretch_aligned(self.justify_self)
393            {
394                return grid_area_minus_item_margins_size.width;
395            }
396
397            None
398        });
399        // Reapply aspect ratio after stretch and absolute position width adjustments
400        let Size { width, height } =
401            Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
402
403        let height = height.or_else(|| {
404            // A height that is a sizing keyword is not auto, so it does not stretch. The stretch
405            // keyword resolves to an exact height; the others resolve during content measurement.
406            if self.size.height.is_sizing_keyword() {
407                return match resolve_sizing_keyword(
408                    self.size.height,
409                    grid_area_minus_item_margins_size.height,
410                    grid_area_size.height,
411                    |val, basis| tree.calc(val, basis),
412                ) {
413                    Some(SizingKeywordResolution::Exact(height)) => Some(height),
414                    _ => None,
415                };
416            }
417
418            // Apply height based on stretch alignment if:
419            //  - Alignment style is "stretch" or "normal" (see the note on width above)
420            //  - The node is not absolutely positioned
421            //  - The node does not have auto margins in this axis.
422            if !self.margin.top.is_auto() && !self.margin.bottom.is_auto() && self.is_stretch_aligned(self.align_self) {
423                return grid_area_minus_item_margins_size.height;
424            }
425
426            None
427        });
428        // Reapply aspect ratio after stretch and absolute position height adjustments
429        let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
430
431        // Clamp size by min and max width/height
432        let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
433
434        Size { width, height }
435    }
436
437    /// Returns the grid area's size in the specified axis when every spanned track has a definite fixed size.
438    ///
439    /// During intrinsic sizing, percentages on grid items resolve against the size of the grid area,
440    /// not the grid container. If the spanned tracks in an axis are not all definite yet, the grid
441    /// area is still indefinite in that axis and percentage-dependent values must stay unresolved here.
442    ///
443    /// Spec:
444    /// https://www.w3.org/TR/css-grid-1/#grid-item-sizing
445    /// https://www.w3.org/TR/css-grid-1/#algo-overview
446    ///
447    /// Compute the available_space to be passed to the child sizing functions
448    /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
449    /// axis to the one currently being sized.
450    /// https://www.w3.org/TR/css-grid-1/#algo-overview
451    pub fn grid_area_size(
452        &self,
453        axis: AbstractAxis,
454        axis_tracks: &[GridTrack],
455        other_axis_tracks: &[GridTrack],
456        available_space: Size<Option<f32>>,
457        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
458        resolve_calc_value: &impl Fn(*const (), f32) -> f32,
459    ) -> Size<Option<f32>> {
460        let mut size = Size::NONE;
461        size.set(
462            axis,
463            axis_tracks[self.track_range_excluding_lines(axis)]
464                .iter()
465                .map(|track| {
466                    let min_size = track
467                        .min_track_sizing_function
468                        .definite_value(available_space.get(axis), resolve_calc_value)?;
469                    let max_size = track
470                        .max_track_sizing_function
471                        .definite_value(available_space.get(axis), resolve_calc_value)?;
472
473                    if min_size == max_size {
474                        Some(track.base_size)
475                    } else {
476                        None
477                    }
478                })
479                .sum::<Option<f32>>(),
480        );
481
482        size.set(
483            axis.other(),
484            other_axis_tracks[self.track_range_excluding_lines(axis.other())]
485                .iter()
486                .map(|track| {
487                    get_track_size_estimate(track, available_space.get(axis.other()))
488                        .map(|size| size + track.content_alignment_adjustment)
489                })
490                .sum::<Option<f32>>(),
491        );
492
493        size
494    }
495
496    /// Retrieve the available_space from the cache or compute them using the passed parameters
497    pub fn grid_area_size_cached(
498        &mut self,
499        axis: AbstractAxis,
500        axis_tracks: &[GridTrack],
501        other_axis_tracks: &[GridTrack],
502        available_space: Size<Option<f32>>,
503        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
504        resolve_calc_value: &impl Fn(*const (), f32) -> f32,
505    ) -> Size<Option<f32>> {
506        self.grid_area_size_cache.unwrap_or_else(|| {
507            let grid_area_size = self.grid_area_size(
508                axis,
509                axis_tracks,
510                other_axis_tracks,
511                available_space,
512                get_track_size_estimate,
513                resolve_calc_value,
514            );
515            self.grid_area_size_cache = Some(grid_area_size);
516            grid_area_size
517        })
518    }
519
520    /// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
521    /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
522    #[inline(always)]
523    pub fn margins_axis_sums_with_baseline_shims(
524        &self,
525        inner_node_width: Option<f32>,
526        tree: &impl LayoutPartialTree,
527    ) -> Size<f32> {
528        Rect {
529            left: self.margin.left.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
530            right: self.margin.right.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
531            top: self.margin.top.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis))
532                + self.baseline_shim,
533            bottom: self.margin.bottom.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis)),
534        }
535        .sum_axes()
536    }
537
538    /// Compute the item's min content contribution from the provided parameters
539    pub fn min_content_contribution(
540        &mut self,
541        axis: AbstractAxis,
542        tree: &mut impl LayoutPartialTree,
543        grid_area_size: Size<Option<f32>>,
544        available_space: Size<Option<f32>>,
545    ) -> f32 {
546        let known_dimensions = self.known_dimensions_cached(tree, grid_area_size);
547        // If the item's size in the axis being measured is already known then that size is its contribution,
548        // and we can avoid calling into the child entirely.
549        if let Some(size) = known_dimensions.get(axis) {
550            return size;
551        }
552        // The child sees the grid area as its containing block during intrinsic measurement, so
553        // percentage box properties resolve against the grid area when that size is definite.
554        // Spec:
555        // https://www.w3.org/TR/css-grid-1/#grid-item-sizing
556        // https://www.w3.org/TR/css-grid-1/#algo-overview
557        tree.measure_child_size(
558            self.node,
559            known_dimensions,
560            grid_area_size,
561            self.keyword_adjusted_available_space(
562                grid_area_size,
563                self.available_space_minus_margins(grid_area_size, available_space, tree).map(|opt| match opt {
564                    Some(size) => AvailableSpace::Definite(size),
565                    None => AvailableSpace::MinContent,
566                }),
567                tree,
568            ),
569            SizingMode::InherentSize,
570            axis.as_abs_naive(),
571            Line::FALSE,
572        )
573    }
574
575    /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
576    #[inline(always)]
577    pub fn min_content_contribution_cached(
578        &mut self,
579        axis: AbstractAxis,
580        tree: &mut impl LayoutPartialTree,
581        grid_area_size: Size<Option<f32>>,
582        available_space: Size<Option<f32>>,
583    ) -> f32 {
584        self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
585            let size = self.min_content_contribution(axis, tree, grid_area_size, available_space);
586            self.min_content_contribution_cache.set(axis, Some(size));
587            size
588        })
589    }
590
591    /// Compute the item's max content contribution from the provided parameters
592    pub fn max_content_contribution(
593        &mut self,
594        axis: AbstractAxis,
595        tree: &mut impl LayoutPartialTree,
596        grid_area_size: Size<Option<f32>>,
597        available_space: Size<Option<f32>>,
598    ) -> f32 {
599        let known_dimensions = self.known_dimensions_cached(tree, grid_area_size);
600        // If the item's size in the axis being measured is already known then that size is its contribution,
601        // and we can avoid calling into the child entirely.
602        if let Some(size) = known_dimensions.get(axis) {
603            return size;
604        }
605        // See the min-content path above. Max-content measurement uses the same containing-block
606        // basis so percentage-dependent item geometry is measured from the grid area rather than
607        // from the container.
608        tree.measure_child_size(
609            self.node,
610            known_dimensions,
611            grid_area_size,
612            self.keyword_adjusted_available_space(
613                grid_area_size,
614                self.available_space_minus_margins(grid_area_size, available_space, tree).map(|opt| match opt {
615                    Some(size) => AvailableSpace::Definite(size),
616                    None => AvailableSpace::MaxContent,
617                }),
618                tree,
619            ),
620            SizingMode::InherentSize,
621            axis.as_abs_naive(),
622            Line::FALSE,
623        )
624    }
625
626    /// Subtract the item's margins (and baseline shim) from the definite axes of the space available
627    /// to the item's margin box, giving the available space that is passed to the item.
628    /// As when the item is laid out into its final position, margins resolve against the width
629    /// of the grid area and `auto` margins are treated as zero.
630    #[inline(always)]
631    fn available_space_minus_margins(
632        &self,
633        grid_area_size: Size<Option<f32>>,
634        available_space: Size<Option<f32>>,
635        tree: &impl LayoutPartialTree,
636    ) -> Size<Option<f32>> {
637        if available_space.width.is_none() && available_space.height.is_none() {
638            return available_space;
639        }
640        let margin = self.margin.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
641        Size {
642            width: available_space.width.map(|width| f32_max(width - margin.horizontal_axis_sum(), 0.0)),
643            height: available_space
644                .height
645                .map(|height| f32_max(height - margin.vertical_axis_sum() - self.baseline_shim, 0.0)),
646        }
647    }
648
649    /// Override the available space in each axis whose size style is a sizing keyword that
650    /// measures the item under a specific available space constraint
651    /// (min-content, max-content, fit-content, fit-content(...))
652    fn keyword_adjusted_available_space(
653        &self,
654        grid_area_size: Size<Option<f32>>,
655        available_space: Size<AvailableSpace>,
656        tree: &impl LayoutPartialTree,
657    ) -> Size<AvailableSpace> {
658        if !self.size.width.is_sizing_keyword() && !self.size.height.is_sizing_keyword() {
659            return available_space;
660        }
661        let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
662        let mut adjusted = available_space;
663        for axis in [AbstractAxis::Inline, AbstractAxis::Block] {
664            let size_style = self.size.get(axis);
665            if !size_style.is_sizing_keyword() {
666                continue;
667            }
668            let stretch_size = grid_area_size.get(axis).maybe_sub(margins.get(axis)).maybe_max(0.0);
669            if let Some(SizingKeywordResolution::Measure(available)) =
670                resolve_sizing_keyword(size_style, stretch_size, grid_area_size.get(axis), |val, basis| {
671                    tree.calc(val, basis)
672                })
673            {
674                adjusted.set(axis, available);
675            }
676        }
677        adjusted
678    }
679
680    /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
681    #[inline(always)]
682    pub fn max_content_contribution_cached(
683        &mut self,
684        axis: AbstractAxis,
685        tree: &mut impl LayoutPartialTree,
686        grid_area_size: Size<Option<f32>>,
687        available_space: Size<Option<f32>>,
688    ) -> f32 {
689        self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
690            let size = self.max_content_contribution(axis, tree, grid_area_size, available_space);
691            self.max_content_contribution_cache.set(axis, Some(size));
692            size
693        })
694    }
695
696    /// The minimum contribution of an item is the smallest outer size it can have.
697    /// Specifically:
698    ///   - If the item’s computed preferred size behaves as auto or depends on the size of its containing block in the relevant axis:
699    ///     Its minimum contribution is the outer size that would result from assuming the item’s used minimum size as its preferred size;
700    ///   - Else the item’s minimum contribution is its min-content contribution.
701    ///
702    /// Because the minimum contribution often depends on the size of the item’s content, it is considered a type of intrinsic size contribution.
703    /// See: https://www.w3.org/TR/css-grid-1/#min-size-auto
704    pub fn minimum_contribution(
705        &mut self,
706        tree: &mut impl LayoutPartialTree,
707        axis: AbstractAxis,
708        axis_tracks: &[GridTrack],
709        grid_area_size: Size<Option<f32>>,
710        inner_node_size: Size<Option<f32>>,
711    ) -> f32 {
712        let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
713        let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
714        let padding_border_size = (padding + border).sum_axes();
715        let box_sizing_adjustment =
716            if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
717        self.size
718            .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
719            .maybe_apply_aspect_ratio(self.aspect_ratio)
720            .maybe_add(box_sizing_adjustment)
721            .get(axis)
722            .map(|size| {
723                // A definite preferred size is clamped by the minimum and maximum sizes. For a replaced element (but
724                // not a form control), an indefinite percentage maximum size is resolved against zero. The spec also
725                // applies this to form controls (https://drafts.csswg.org/css-sizing-3/#min-content-zero) but no
726                // browser does (https://github.com/w3c/csswg-drafts/issues/6348).
727                let min_size = self.min_size.get(axis);
728                let max_size = self.max_size.get(axis);
729                if min_size.is_auto() && max_size.is_auto() {
730                    return size;
731                }
732                let basis = grid_area_size.get(axis);
733                let max_size_basis = if self.is_replaced { basis.or(Some(0.0)) } else { basis };
734                let adjustment = box_sizing_adjustment.get(axis);
735                let min_size = min_size.maybe_resolve(basis, |val, basis| tree.calc(val, basis));
736                let max_size = max_size.maybe_resolve(max_size_basis, |val, basis| tree.calc(val, basis));
737                size.maybe_clamp(min_size.maybe_add(adjustment), max_size.maybe_add(adjustment))
738            })
739            .or_else(|| {
740                // A preferred size that is a sizing keyword other than `stretch` does not behave as auto,
741                // so the minimum contribution is the min-content contribution.
742                let size = self.size.get(axis);
743                (size.is_sizing_keyword() && !size.is_stretch())
744                    .then(|| self.min_content_contribution_cached(axis, tree, grid_area_size, grid_area_size))
745            })
746            .or_else(|| {
747                self.min_size
748                    .maybe_resolve(grid_area_size.map(|size| size.unwrap_or(0.0)), |val, basis| tree.calc(val, basis))
749                    .maybe_apply_aspect_ratio(self.aspect_ratio)
750                    .maybe_add(box_sizing_adjustment)
751                    .get(axis)
752            })
753            .or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
754            .unwrap_or_else(|| {
755                // Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
756
757                // To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size
758                // in a given axis is the content-based minimum size if all of the following are true:
759                let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
760
761                // it is not a scroll container (handled above)
762
763                // it spans at least one track in that axis whose min track sizing function is auto
764                let spans_auto_min_track = item_axis_tracks
765                    .iter()
766                    .any(|track| track.min_track_sizing_function.behaves_as_auto(inner_node_size.get(axis)));
767
768                // if it spans more than one track in that axis, none of those tracks are flexible
769                let only_span_one_track = item_axis_tracks.len() == 1;
770                let use_content_based_minimum = spans_auto_min_track
771                    && (only_span_one_track
772                        || !item_axis_tracks.iter().any(|track| track.max_track_sizing_function.is_fr()));
773
774                // Otherwise, the automatic minimum size is zero, as usual.
775                if use_content_based_minimum {
776                    let mut minimum_contribution =
777                        self.min_content_contribution_cached(axis, tree, grid_area_size, grid_area_size);
778
779                    // If the item is a compressible replaced element, and has a definite preferred size or maximum size in the
780                    // relevant axis, the size suggestion is capped by those sizes; for this purpose, any indefinite percentages
781                    // in these sizes are resolved against zero (and considered definite).
782                    if self.is_compressible_replaced {
783                        let size = self.size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
784                        let max_size =
785                            self.max_size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
786                        minimum_contribution = minimum_contribution.maybe_min(size).maybe_min(max_size);
787                    }
788
789                    // The content-based minimum size is additionally clamped by the sum of any fixed max track sizing
790                    // functions of the tracks the item spans. Note that this clamp does not apply to explicitly specified
791                    // preferred or minimum sizes, and that the argument to fit-content() does not clamp the content-based
792                    // minimum size in the same way as a fixed max track sizing function. The clamp is a stretch fit into
793                    // the limit, so it does not shrink the item below its padding + border.
794                    let limit =
795                        self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis), &|val, basis| {
796                            tree.resolve_calc_value(val, basis)
797                        });
798                    minimum_contribution.maybe_min(limit.map(|limit| f32_max(limit, padding_border_size.get(axis))))
799                } else {
800                    0.0
801                }
802            })
803    }
804
805    /// Retrieve the item's minimum contribution from the cache or compute it using the provided parameters
806    #[inline(always)]
807    pub fn minimum_contribution_cached(
808        &mut self,
809        tree: &mut impl LayoutPartialTree,
810        axis: AbstractAxis,
811        axis_tracks: &[GridTrack],
812        grid_area_size: Size<Option<f32>>,
813        inner_node_size: Size<Option<f32>>,
814    ) -> f32 {
815        self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
816            let size = self.minimum_contribution(tree, axis, axis_tracks, grid_area_size, inner_node_size);
817            self.minimum_contribution_cache.set(axis, Some(size));
818            size
819        })
820    }
821}