Skip to main content

taffy/style/
grid.rs

1//! Style types for CSS Grid layout
2use super::{
3    AlignContent, AlignContentKeyword, AlignItems, AlignSelf, CheapCloneStr, CompactLength, CoreStyle, Dimension,
4    JustifyContent, LengthPercentage, LengthPercentageAuto, Style,
5};
6use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine, MAX_GRID_TRACKS};
7use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
8use crate::style_helpers::*;
9use crate::sys::{DefaultCheapStr, Vec};
10use core::cmp::{max, min};
11use core::fmt::Debug;
12
13#[cfg(feature = "parse")]
14use crate::util::parse::{
15    from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, ParseError, Parser, Token,
16};
17
18/// Defines the value of the `grid-template-areas` property: the named areas plus the overall
19/// size (in tracks) of the area template.
20///
21/// The template may be larger than the extents of the named areas due to unnamed (`.`) cells,
22/// so the size is stored explicitly.
23#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct GridTemplateAreas<CustomIdent: CheapCloneStr> {
26    /// The named grid areas
27    pub areas: crate::util::sys::GridTrackVec<GridTemplateArea<CustomIdent>>,
28    /// The number of rows in the area template
29    pub row_count: u16,
30    /// The number of columns in the area template
31    pub column_count: u16,
32}
33
34/// Defines a grid area
35#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct GridTemplateArea<CustomIdent: CheapCloneStr> {
38    /// The name of the grid area which
39    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
40    pub name: CustomIdent,
41    /// The index of the row at which the grid area starts in grid coordinates.
42    pub row_start: u16,
43    /// The index of the row at which the grid area ends in grid coordinates.
44    pub row_end: u16,
45    /// The index of the column at which the grid area starts in grid coordinates.
46    pub column_start: u16,
47    /// The index of the column at which the grid area end in grid coordinates.
48    pub column_end: u16,
49}
50
51/// Defines a named grid line
52#[derive(Debug, Clone, PartialEq)]
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54pub struct NamedGridLine<CustomIdent: CheapCloneStr> {
55    /// The name of the grid area which
56    #[cfg_attr(feature = "serde", serde(deserialize_with = "crate::util::deserialize_from_str"))]
57    pub name: CustomIdent,
58    /// The index of the row at which the grid area starts in grid coordinates.
59    pub index: u16,
60}
61
62/// Axis as `Row` or `Column`
63#[derive(Debug, Clone, Copy, PartialEq)]
64pub(crate) enum GridAreaAxis {
65    /// The `Row` axis
66    Row,
67    /// The `Column` axis
68    Column,
69}
70
71/// Logical end (`Start` or `End`)
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub(crate) enum GridAreaEnd {
74    /// The `Start` end
75    Start,
76    /// The `End` end
77    End,
78}
79
80/// A trait to represent a `repeat()` clause in a `grid-template-*` definition
81pub trait GenericRepetition {
82    /// The type that represents `<custom-ident>`s (for named lines)
83    type CustomIdent: CheapCloneStr;
84    /// The type which represents an iterator over the list of repeated tracks
85    type RepetitionTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
86    where
87        Self: 'a;
88
89    /// A nested iterator of line names (nested because each line may have multiple associated names)
90    type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
91    where
92        Self: 'a;
93    /// The repetition count (integer, auto-fill, or auto-fit)
94    fn count(&self) -> RepetitionCount;
95    /// Get an iterator over the repeated tracks
96    fn tracks(&self) -> Self::RepetitionTrackList<'_>;
97    /// Returns the number of repeated tracks
98    fn track_count(&self) -> u16 {
99        self.tracks().len().min(u16::MAX as usize) as u16
100    }
101    /// Returns an iterator over the lines names
102    ///
103    /// Line name sets are positional: set `i` names the `i`th line of each repetition. The iterator
104    /// must yield either no line name sets at all (all lines are unnamed) or exactly
105    /// `track_count() + 1` of them (one set per line, including both edge lines).
106    fn lines_names(&self) -> Self::TemplateLineNames<'_>;
107}
108
109/// A nested list of line names. This is effectively a generic representation of `Vec<Vec<String>>` that allows
110/// both the collection and string type to be customised.
111#[rustfmt::skip]
112pub trait TemplateLineNames<'a, S: CheapCloneStr> : Iterator<Item = Self::LineNameSet<'a>> + ExactSizeIterator + Clone where Self: 'a {
113    /// A simple list line names. This is effectively a generic representation of `VecString>` that allows
114    /// both the collection and string type to be customised.
115    type LineNameSet<'b>: Iterator<Item = &'b S> + ExactSizeIterator + Clone where Self: 'b;
116}
117
118impl<'a, S: CheapCloneStr> TemplateLineNames<'a, S>
119    for core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>>
120{
121    type LineNameSet<'b>
122        = core::slice::Iter<'b, S>
123    where
124        Self: 'b;
125}
126
127#[derive(Copy, Clone)]
128/// A type representing a component in a `grid-template-*` defintion where the type
129/// representing `repeat()`s is generic
130pub enum GenericGridTemplateComponent<S, Repetition>
131where
132    S: CheapCloneStr,
133    Repetition: GenericRepetition<CustomIdent = S>,
134{
135    /// A single track sizing function
136    Single(TrackSizingFunction),
137    /// A `repeat()`
138    Repeat(Repetition),
139}
140
141impl<S, Repetition> GenericGridTemplateComponent<S, Repetition>
142where
143    S: CheapCloneStr,
144    Repetition: GenericRepetition<CustomIdent = S>,
145{
146    /// Whether the track definition is a auto-repeated fragment
147    pub fn is_auto_repetition(&self) -> bool {
148        match self {
149            Self::Single(_) => false,
150            Self::Repeat(repeat) => matches!(repeat.count(), RepetitionCount::AutoFit | RepetitionCount::AutoFill),
151        }
152    }
153}
154
155/// The set of styles required for a CSS Grid container
156pub trait GridContainerStyle: CoreStyle {
157    /// The type for a `repeat()` within a grid_template_rows or grid_template_columns
158    type Repetition<'a>: GenericRepetition<CustomIdent = Self::CustomIdent>
159    where
160        Self: 'a;
161
162    /// The type returned by grid_template_rows and grid_template_columns
163    type TemplateTrackList<'a>: Iterator<Item = GenericGridTemplateComponent<Self::CustomIdent, Self::Repetition<'a>>>
164        + ExactSizeIterator
165        + Clone
166    where
167        Self: 'a;
168
169    /// The type returned by grid_auto_rows and grid_auto_columns
170    type AutoTrackList<'a>: Iterator<Item = TrackSizingFunction> + ExactSizeIterator + Clone
171    where
172        Self: 'a;
173
174    /// The type returned by grid_template_row_names and grid_template_column_names
175    //IntoIterator<Item = &'a Self::LineNameSet<'a>>
176    type TemplateLineNames<'a>: TemplateLineNames<'a, Self::CustomIdent>
177    where
178        Self: 'a;
179
180    /// The type of custom identifiers used to identify named grid lines and areas
181    type GridTemplateAreas<'a>: IntoIterator<Item = GridTemplateArea<Self::CustomIdent>>
182    where
183        Self: 'a;
184
185    // FIXME: re-add default implemenations for grid_{template,auto}_{rows,columns} once the
186    // associated_type_defaults feature (https://github.com/rust-lang/rust/issues/29661) is stabilised.
187
188    /// Defines the track sizing functions (heights) of the grid rows
189    fn grid_template_rows(&self) -> Option<Self::TemplateTrackList<'_>>;
190    /// Defines the track sizing functions (widths) of the grid columns
191    fn grid_template_columns(&self) -> Option<Self::TemplateTrackList<'_>>;
192    /// Defines the size of implicitly created rows
193    fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
194    /// Defined the size of implicitly created columns
195    fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
196
197    /// Named grid areas
198    fn grid_template_areas(&self) -> Option<Self::GridTemplateAreas<'_>>;
199    /// The number of rows in the `grid-template-areas` template (0 if there is no template).
200    /// May be greater than the extent of the named areas due to unnamed (`.`) cells.
201    fn grid_template_area_row_count(&self) -> u16 {
202        self.grid_template_areas()
203            .map(|areas| areas.into_iter().map(|area| area.row_end.max(1) - 1).max().unwrap_or(0))
204            .unwrap_or(0)
205    }
206    /// The number of columns in the `grid-template-areas` template (0 if there is no template).
207    /// May be greater than the extent of the named areas due to unnamed (`.`) cells.
208    fn grid_template_area_column_count(&self) -> u16 {
209        self.grid_template_areas()
210            .map(|areas| areas.into_iter().map(|area| area.column_end.max(1) - 1).max().unwrap_or(0))
211            .unwrap_or(0)
212    }
213    /// Defines the line names for row lines
214    fn grid_template_column_names(&self) -> Option<Self::TemplateLineNames<'_>>;
215    /// Defines the size of implicitly created rows
216    fn grid_template_row_names(&self) -> Option<Self::TemplateLineNames<'_>>;
217
218    /// Controls how items get placed into the grid for auto-placed items
219    #[inline(always)]
220    fn grid_auto_flow(&self) -> GridAutoFlow {
221        Style::<Self::CustomIdent>::DEFAULT.grid_auto_flow
222    }
223
224    /// How large should the gaps between items in a grid or flex container be?
225    #[inline(always)]
226    fn gap(&self) -> Size<LengthPercentage> {
227        Style::<Self::CustomIdent>::DEFAULT.gap
228    }
229
230    // Alignment properties
231
232    /// How should content contained within this item be aligned in the cross/block axis
233    #[inline(always)]
234    fn align_content(&self) -> AlignContent {
235        Style::<Self::CustomIdent>::DEFAULT.align_content
236    }
237    /// How should contained within this item be aligned in the main/inline axis
238    #[inline(always)]
239    fn justify_content(&self) -> JustifyContent {
240        Style::<Self::CustomIdent>::DEFAULT.justify_content
241    }
242    /// How this node's children aligned in the cross/block axis?
243    #[inline(always)]
244    fn align_items(&self) -> AlignItems {
245        Style::<Self::CustomIdent>::DEFAULT.align_items
246    }
247    /// How this node's children should be aligned in the inline axis
248    #[inline(always)]
249    fn justify_items(&self) -> AlignItems {
250        Style::<Self::CustomIdent>::DEFAULT.justify_items
251    }
252
253    /// Get a grid item's row or column placement depending on the axis passed
254    #[inline(always)]
255    fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Option<Self::TemplateTrackList<'_>> {
256        match axis {
257            AbsoluteAxis::Horizontal => self.grid_template_columns(),
258            AbsoluteAxis::Vertical => self.grid_template_rows(),
259        }
260    }
261
262    /// Get a grid container's align-content or justify-content alignment depending on the axis passed
263    /// (with `normal` resolved to `stretch`)
264    #[inline(always)]
265    fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
266        let alignment = match axis {
267            AbstractAxis::Inline => self.justify_content(),
268            AbstractAxis::Block => self.align_content(),
269        };
270        match alignment.keyword {
271            AlignContentKeyword::Normal => AlignContent::STRETCH,
272            _ => alignment,
273        }
274    }
275}
276
277/// The set of styles required for a CSS Grid item (child of a CSS Grid container)
278pub trait GridItemStyle: CoreStyle {
279    /// Defines which row in the grid the item should start and end at
280    #[inline(always)]
281    fn grid_row(&self) -> Line<GridPlacement<Self::CustomIdent>> {
282        Default::default()
283    }
284    /// Defines which column in the grid the item should start and end at
285    #[inline(always)]
286    fn grid_column(&self) -> Line<GridPlacement<Self::CustomIdent>> {
287        Default::default()
288    }
289
290    /// How this node should be aligned in the cross/block axis
291    /// Falls back to the parents [`AlignItems`] if not set
292    #[inline(always)]
293    fn align_self(&self) -> Option<AlignSelf> {
294        Style::<Self::CustomIdent>::DEFAULT.align_self
295    }
296    /// How this node should be aligned in the inline axis
297    /// Falls back to the parents [`super::JustifyItems`] if not set
298    #[inline(always)]
299    fn justify_self(&self) -> Option<AlignSelf> {
300        Style::<Self::CustomIdent>::DEFAULT.justify_self
301    }
302
303    /// Get a grid item's row or column placement depending on the axis passed
304    #[inline(always)]
305    fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement<Self::CustomIdent>> {
306        match axis {
307            AbsoluteAxis::Horizontal => self.grid_column(),
308            AbsoluteAxis::Vertical => self.grid_row(),
309        }
310    }
311}
312
313/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
314///
315/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.
316///
317/// Defaults to [`GridAutoFlow::Row`]
318///
319/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
320#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
321#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
322pub enum GridAutoFlow {
323    /// Items are placed by filling each row in turn, adding new rows as necessary
324    #[default]
325    Row,
326    /// Items are placed by filling each column in turn, adding new columns as necessary.
327    Column,
328    /// Combines `Row` with the dense packing algorithm.
329    RowDense,
330    /// Combines `Column` with the dense packing algorithm.
331    ColumnDense,
332}
333
334#[cfg(feature = "parse")]
335impl FromCss for GridAutoFlow {
336    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
337        let mut axis: Option<&'static str> = None;
338        let mut dense = false;
339
340        for _ in 0..2 {
341            if let Ok(ident) = parser.try_parse(|parser| parser.expect_ident_cloned()) {
342                match &*ident {
343                    "row" => {
344                        axis = Some("row");
345                    }
346                    "column" => {
347                        axis = Some("column");
348                    }
349                    "dense" => dense = true,
350                    _ => {
351                        return Err(parser.new_unexpected_token_error(Token::Ident(ident)));
352                    }
353                }
354            } else {
355                break;
356            }
357        }
358
359        match (axis, dense) {
360            (Some("row"), false) => Ok(Self::Row),
361            (Some("row") | None, true) => Ok(Self::RowDense),
362            (Some("column"), false) => Ok(Self::Column),
363            (Some("column"), true) => Ok(Self::ColumnDense),
364            (None, false) => {
365                let token = parser.next().cloned()?;
366                Err(parser.new_unexpected_token_error(token))
367            }
368            _ => unreachable!(),
369        }
370    }
371}
372#[cfg(feature = "parse")]
373from_str_from_css!(GridAutoFlow);
374
375impl GridAutoFlow {
376    /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm
377    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
378    pub const fn is_dense(&self) -> bool {
379        match self {
380            Self::Row | Self::Column => false,
381            Self::RowDense | Self::ColumnDense => true,
382        }
383    }
384
385    /// Whether grid auto placement fills areas row-wise or column-wise
386    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
387    pub const fn primary_axis(&self) -> AbsoluteAxis {
388        match self {
389            Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
390            Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
391        }
392    }
393}
394
395/// A grid line placement specification which is generic over the coordinate system that it uses to define
396/// grid line positions.
397///
398/// `GenericGridPlacement<GridLine>` is aliased as GridPlacement and is exposed to users of Taffy to define styles.
399/// `GenericGridPlacement<OriginZeroLine>` is aliased as OriginZeroGridPlacement and is used internally for placement computations.
400#[derive(Copy, Clone, PartialEq, Eq, Debug)]
401#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
402pub enum GenericGridPlacement<LineType: GridCoordinate> {
403    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
404    Auto,
405    /// Place item at specified line (column or row) index
406    Line(LineType),
407    /// Item should span specified number of tracks (columns or rows)
408    Span(u16),
409}
410
411/// A grid line placement using the normalized OriginZero coordinates to specify line positions.
412pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
413
414/// A grid line placement using CSS grid line coordinates to specify line positions. This uses the same coordinate
415/// system as the public `GridPlacement` type but doesn't support named lines (these are expected to have already
416/// been resolved by the time values of this type are constructed).
417pub(crate) type NonNamedGridPlacement = GenericGridPlacement<GridLine>;
418
419/// A grid line placement specification. Used for grid-[row/column]-[start/end]. Named tracks are not implemented.
420///
421/// Defaults to `GridPlacement::Auto`
422///
423/// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line)
424#[derive(Clone, PartialEq, Debug, Default)]
425#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
426pub enum GridPlacement<S: CheapCloneStr = DefaultCheapStr> {
427    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
428    #[default]
429    Auto,
430    /// Place item at specified line (column or row) index
431    Line(GridLine),
432    /// Place item at specified named line (column or row)
433    NamedLine(S, i16),
434    /// Item should span specified number of tracks (columns or rows)
435    Span(u16),
436    /// Item should span until the nth line named `<name>`.
437    ///
438    /// If there are less than n lines named `<name>` in the specified direction then
439    /// all implicit lines will be counted.
440    NamedSpan(S, u16),
441}
442impl<S: CheapCloneStr> TaffyAuto for GridPlacement<S> {
443    const AUTO: Self = Self::Auto;
444}
445impl<S: CheapCloneStr> TaffyGridLine for GridPlacement<S> {
446    fn from_line_index(index: i16) -> Self {
447        GridPlacement::<S>::Line(GridLine::from(index))
448    }
449}
450impl<S: CheapCloneStr> TaffyGridLine for Line<GridPlacement<S>> {
451    fn from_line_index(index: i16) -> Self {
452        Line { start: GridPlacement::<S>::from_line_index(index), end: GridPlacement::<S>::Auto }
453    }
454}
455impl<S: CheapCloneStr> TaffyGridSpan for GridPlacement<S> {
456    fn from_span(span: u16) -> Self {
457        GridPlacement::<S>::Span(span)
458    }
459}
460impl<S: CheapCloneStr> TaffyGridSpan for Line<GridPlacement<S>> {
461    fn from_span(span: u16) -> Self {
462        Line { start: GridPlacement::<S>::from_span(span), end: GridPlacement::<S>::Auto }
463    }
464}
465
466#[cfg(feature = "parse")]
467/// Saturates an `i32` to the range representable by `i16`.
468fn saturating_i16(value: i32) -> i16 {
469    value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
470}
471
472#[cfg(feature = "parse")]
473/// Saturates an `i32` to the range representable by `u16`.
474fn saturating_u16(value: i32) -> u16 {
475    value.clamp(u16::MIN as i32, u16::MAX as i32) as u16
476}
477
478#[cfg(feature = "parse")]
479impl<S: CheapCloneStr> FromCss for GridPlacement<S> {
480    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
481        let mut span = false;
482        let mut number = None;
483        let mut ident = None;
484
485        while !parser.is_exhausted() {
486            let token = parser.next()?.clone();
487            match &token {
488                Token::Ident(s) => match s.as_ref() {
489                    "auto" => {
490                        if span || number.is_some() || ident.is_some() {
491                            return Err(parser.new_unexpected_token_error(token));
492                        }
493                        parser.expect_exhausted()?;
494                        return Ok(Self::Auto);
495                    }
496                    "span" => {
497                        if span {
498                            return Err(parser.new_unexpected_token_error(token));
499                        }
500                        span = true;
501                    }
502                    other => {
503                        if ident.is_some() {
504                            return Err(parser.new_unexpected_token_error(token));
505                        }
506                        ident = Some(S::from(other));
507                    }
508                },
509                Token::Number { int_value: Some(value), .. } if *value != 0 => {
510                    if number.is_some() {
511                        return Err(parser.new_unexpected_token_error(token));
512                    }
513                    number = Some(*value);
514                }
515                _ => return Err(parser.new_unexpected_token_error(token)),
516            };
517        }
518
519        match (span, number, ident) {
520            (true, None, None) => Ok(Self::Span(0)),
521            (true, Some(number), None) => Ok(Self::Span(saturating_u16(number))),
522            (true, None, Some(ident)) => Ok(Self::NamedSpan(ident, 0)),
523            (true, Some(number), Some(ident)) => Ok(Self::NamedSpan(ident, saturating_u16(number))),
524            (false, Some(number), None) => Ok(Self::Line(GridLine::from(saturating_i16(number)))),
525            (false, Some(number), Some(ident)) => Ok(Self::NamedLine(ident, saturating_i16(number))),
526            (false, None, Some(ident)) => Ok(Self::NamedLine(ident, 0)),
527            (false, None, None) => Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput)),
528        }
529    }
530}
531
532#[cfg(feature = "parse")]
533impl<S: CheapCloneStr> core::str::FromStr for GridPlacement<S> {
534    type Err = ParseError;
535    fn from_str(input: &str) -> Result<Self, Self::Err> {
536        parse_css_str_entirely(input)
537    }
538}
539
540impl<S: CheapCloneStr> GridPlacement<S> {
541    /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified.
542    pub fn into_origin_zero_placement_ignoring_named(&self, explicit_track_count: u16) -> OriginZeroGridPlacement {
543        match self {
544            Self::Auto => OriginZeroGridPlacement::Auto,
545            // Spans are clamped between 1 (a zero span is an invalid value which is treated as 1)
546            // and the maximum track limit (https://www.w3.org/TR/css-grid-1/#overlarge-grids)
547            Self::Span(span) => OriginZeroGridPlacement::Span((*span).clamp(1, MAX_GRID_TRACKS)),
548            // Grid line zero is an invalid index, so it gets treated as Auto
549            // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
550            Self::Line(line) => match line.as_i16() {
551                0 => OriginZeroGridPlacement::Auto,
552                _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
553            },
554            Self::NamedLine(_, _) => OriginZeroGridPlacement::Auto,
555            Self::NamedSpan(_, _) => OriginZeroGridPlacement::Auto,
556        }
557    }
558}
559
560impl<S: CheapCloneStr> Line<GridPlacement<S>> {
561    /// Apply a mapping function if the [`GridPlacement`] is a `Line`. Otherwise return `self` unmodified.
562    pub fn into_origin_zero_ignoring_named(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
563        Line {
564            start: self.start.into_origin_zero_placement_ignoring_named(explicit_track_count),
565            end: self.end.into_origin_zero_placement_ignoring_named(explicit_track_count),
566        }
567    }
568}
569
570impl NonNamedGridPlacement {
571    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
572    pub fn into_origin_zero_placement(
573        &self,
574        explicit_track_count: u16,
575        // resolve_named: impl Fn(&str) -> Option<GridLine>
576    ) -> OriginZeroGridPlacement {
577        match self {
578            Self::Auto => OriginZeroGridPlacement::Auto,
579            // Spans are clamped between 1 (a zero span is an invalid value which is treated as 1)
580            // and the maximum track limit (https://www.w3.org/TR/css-grid-1/#overlarge-grids)
581            Self::Span(span) => OriginZeroGridPlacement::Span((*span).clamp(1, MAX_GRID_TRACKS)),
582            // Grid line zero is an invalid index, so it gets treated as Auto
583            // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
584            Self::Line(line) => match line.as_i16() {
585                0 => OriginZeroGridPlacement::Auto,
586                _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
587            },
588        }
589    }
590}
591
592impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
593    /// Resolves the span for an indefinite placement (a placement that does not consist of two `Track`s).
594    /// Panics if called on a definite placement
595    pub const fn indefinite_span(&self) -> u16 {
596        use GenericGridPlacement as GP;
597        match (self.start, self.end) {
598            (GP::Line(_), GP::Auto) => 1,
599            (GP::Auto, GP::Line(_)) => 1,
600            (GP::Auto, GP::Auto) => 1,
601            (GP::Line(_), GP::Span(span)) => span,
602            (GP::Span(span), GP::Line(_)) => span,
603            (GP::Span(span), GP::Auto) => span,
604            (GP::Auto, GP::Span(span)) => span,
605            (GP::Span(span), GP::Span(_)) => span,
606            (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
607        }
608    }
609}
610
611impl<S: CheapCloneStr> Line<GridPlacement<S>> {
612    #[inline]
613    /// Whether the track position is definite in this axis (or the item will need auto placement)
614    /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
615    /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
616    pub fn is_definite(&self) -> bool {
617        match (&self.start, &self.end) {
618            (GridPlacement::Line(line), _) if line.as_i16() != 0 => true,
619            (_, GridPlacement::Line(line)) if line.as_i16() != 0 => true,
620            (GridPlacement::NamedLine(_, _), _) => true,
621            (_, GridPlacement::NamedLine(_, _)) => true,
622            _ => false,
623        }
624    }
625}
626
627impl Line<NonNamedGridPlacement> {
628    #[inline]
629    /// Whether the track position is definite in this axis (or the item will need auto placement)
630    /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
631    /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
632    pub fn is_definite(&self) -> bool {
633        match (&self.start, &self.end) {
634            (GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
635            (_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
636            _ => false,
637        }
638    }
639
640    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
641    pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
642        Line {
643            start: self.start.into_origin_zero_placement(explicit_track_count),
644            end: self.end.into_origin_zero_placement(explicit_track_count),
645        }
646    }
647}
648
649impl Line<OriginZeroGridPlacement> {
650    #[inline]
651    /// Whether the track position is definite in this axis (or the item will need auto placement)
652    /// The track position is definite if least one of the start and end positions is a track index
653    pub const fn is_definite(&self) -> bool {
654        matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
655    }
656
657    /// If at least one of the of the start and end positions is a track index then the other end can be resolved
658    /// into a track index purely based on the information contained with the placement specification
659    pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
660        use OriginZeroGridPlacement as GP;
661        match (self.start, self.end) {
662            (GP::Line(line1), GP::Line(line2)) => {
663                if line1 == line2 {
664                    Line { start: line1, end: line1 + 1 }
665                } else {
666                    Line { start: min(line1, line2), end: max(line1, line2) }
667                }
668            }
669            (GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
670            (GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
671            (GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
672            (GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
673            _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
674        }
675    }
676
677    /// For absolutely positioned items:
678    ///   - Tracks resolve to definite tracks
679    ///   - For Spans:
680    ///      - If the other position is a Track, they resolve to a definite track relative to the other track
681    ///      - Else resolve to None
682    ///   - Auto resolves to None
683    ///
684    /// When finally positioning the item, a value of None means that the item's grid area is bounded by the grid
685    /// container's border box on that side.
686    pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
687        use OriginZeroGridPlacement as GP;
688        match (self.start, self.end) {
689            (GP::Line(track1), GP::Line(track2)) => {
690                if track1 == track2 {
691                    Line { start: Some(track1), end: Some(track1 + 1) }
692                } else {
693                    Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
694                }
695            }
696            (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
697            (GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
698            (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
699            (GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
700            _ => Line { start: None, end: None },
701        }
702    }
703
704    /// If neither of the start and end positions is a track index then the other end can be resolved
705    /// into a track index if a definite start position is supplied externally
706    pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
707        use OriginZeroGridPlacement as GP;
708        match (self.start, self.end) {
709            (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
710            (GP::Span(span), GP::Auto) => Line { start, end: start + span },
711            (GP::Auto, GP::Span(span)) => Line { start, end: start + span },
712            (GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
713            _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
714        }
715    }
716}
717
718/// Represents the start and end points of a GridItem within a given axis
719impl<S: CheapCloneStr> Default for Line<GridPlacement<S>> {
720    fn default() -> Self {
721        Line { start: GridPlacement::<S>::Auto, end: GridPlacement::<S>::Auto }
722    }
723}
724
725/// Maximum track sizing function
726///
727/// Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
728/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
729/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
730#[derive(Copy, Clone, PartialEq, Debug)]
731#[cfg_attr(feature = "serde", derive(Serialize))]
732pub struct MaxTrackSizingFunction(pub(crate) CompactLength);
733impl TaffyZero for MaxTrackSizingFunction {
734    const ZERO: Self = Self(CompactLength::ZERO);
735}
736impl TaffyAuto for MaxTrackSizingFunction {
737    const AUTO: Self = Self(CompactLength::AUTO);
738}
739impl TaffyMinContent for MaxTrackSizingFunction {
740    const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
741}
742impl TaffyMaxContent for MaxTrackSizingFunction {
743    const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
744}
745impl FromLength for MaxTrackSizingFunction {
746    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
747        Self::length(value.into() as f32)
748    }
749}
750impl FromPercent for MaxTrackSizingFunction {
751    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
752        Self::percent(value.into() as f32)
753    }
754}
755impl TaffyFitContent for MaxTrackSizingFunction {
756    fn fit_content(argument: LengthPercentage) -> Self {
757        Self(CompactLength::fit_content(argument))
758    }
759}
760impl FromFr for MaxTrackSizingFunction {
761    fn from_fr<Input: Into<f64> + Copy>(value: Input) -> Self {
762        Self::fr(value.into() as f32)
763    }
764}
765impl From<LengthPercentage> for MaxTrackSizingFunction {
766    fn from(input: LengthPercentage) -> Self {
767        Self(input.0)
768    }
769}
770impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
771    fn from(input: LengthPercentageAuto) -> Self {
772        Self(input.0)
773    }
774}
775impl From<Dimension> for MaxTrackSizingFunction {
776    fn from(input: Dimension) -> Self {
777        // Dimension supports values that are not valid max track sizing functions.
778        // Map those to `auto`.
779        match input.0.tag() {
780            CompactLength::FIT_CONTENT_KEYWORD_TAG | CompactLength::STRETCH_TAG | CompactLength::CONTENT_TAG => {
781                Self::auto()
782            }
783            _ => Self(input.0),
784        }
785    }
786}
787impl From<MinTrackSizingFunction> for MaxTrackSizingFunction {
788    fn from(input: MinTrackSizingFunction) -> Self {
789        Self(input.0)
790    }
791}
792
793#[cfg(feature = "parse")]
794impl FromCss for MaxTrackSizingFunction {
795    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
796        let token = parser.next()?.clone();
797        match token {
798            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
799            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
800            Token::Dimension { unit, value, .. } if unit == "fr" && value.is_sign_positive() => Ok(Self::fr(value)),
801            Token::Ident(ref ident) => match ident.as_ref() {
802                "auto" => Ok(Self::auto()),
803                "min-content" => Ok(Self::min_content()),
804                "max-content" => Ok(Self::max_content()),
805                _ => Err(parser.new_unexpected_token_error(token))?,
806            },
807            Token::Function(ref name) if name.as_ref() == "fit-content" => parser.parse_nested_block(|parser| {
808                let token = parser.next()?.clone();
809                match token {
810                    Token::Percentage { unit_value, .. } => Ok(Self::fit_content_percent(unit_value)),
811                    Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::fit_content_px(value)),
812                    token => Err(parser.new_unexpected_token_error(token))?,
813                }
814            }),
815            token => Err(parser.new_unexpected_token_error(token))?,
816        }
817    }
818}
819
820#[cfg(feature = "parse")]
821from_str_from_css!(MaxTrackSizingFunction);
822
823#[cfg(feature = "serde")]
824impl<'de> serde::Deserialize<'de> for MaxTrackSizingFunction {
825    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
826    where
827        D: serde::Deserializer<'de>,
828    {
829        let inner = CompactLength::deserialize(deserializer)?;
830        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
831        if matches!(
832            inner.tag(),
833            CompactLength::LENGTH_TAG
834                | CompactLength::PERCENT_TAG
835                | CompactLength::AUTO_TAG
836                | CompactLength::MIN_CONTENT_TAG
837                | CompactLength::MAX_CONTENT_TAG
838                | CompactLength::FIT_CONTENT_PX_TAG
839                | CompactLength::FIT_CONTENT_PERCENT_TAG
840                | CompactLength::FR_TAG
841        ) {
842            Ok(Self(inner))
843        } else {
844            Err(serde::de::Error::custom("Invalid tag"))
845        }
846    }
847}
848
849impl MaxTrackSizingFunction {
850    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
851    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
852    #[inline(always)]
853    pub const fn length(val: f32) -> Self {
854        Self(CompactLength::length(val))
855    }
856
857    /// A percentage length relative to the size of the containing block.
858    ///
859    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
860    #[inline(always)]
861    pub const fn percent(val: f32) -> Self {
862        Self(CompactLength::percent(val))
863    }
864
865    /// The dimension should be automatically computed according to algorithm-specific rules
866    /// regarding the default size of boxes.
867    #[inline(always)]
868    pub const fn auto() -> Self {
869        Self(CompactLength::auto())
870    }
871
872    /// The size should be the "min-content" size.
873    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
874    #[inline(always)]
875    pub const fn min_content() -> Self {
876        Self(CompactLength::min_content())
877    }
878
879    /// The size should be the "max-content" size.
880    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
881    #[inline(always)]
882    pub const fn max_content() -> Self {
883        Self(CompactLength::max_content())
884    }
885
886    /// The size should be computed according to the "fit content" formula:
887    ///    `max(min_content, min(max_content, limit))`
888    /// where:
889    ///    - `min_content` is the [min-content](Self::min_content) size
890    ///    - `max_content` is the [max-content](Self::max_content) size
891    ///    - `limit` is a LENGTH value passed to this function
892    ///
893    /// The effect of this is that the item takes the size of `limit` clamped
894    /// by the min-content and max-content sizes.
895    #[inline(always)]
896    pub const fn fit_content_px(limit: f32) -> Self {
897        Self(CompactLength::fit_content_px(limit))
898    }
899
900    /// The size should be computed according to the "fit content" formula:
901    ///    `max(min_content, min(max_content, limit))`
902    /// where:
903    ///    - `min_content` is the [min-content](Self::min_content) size
904    ///    - `max_content` is the [max-content](Self::max_content) size
905    ///    - `limit` is a PERCENTAGE value passed to this function
906    ///
907    /// The effect of this is that the item takes the size of `limit` clamped
908    /// by the min-content and max-content sizes.
909    #[inline(always)]
910    pub const fn fit_content_percent(limit: f32) -> Self {
911        Self(CompactLength::fit_content_percent(limit))
912    }
913
914    /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
915    /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
916    /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
917    #[inline(always)]
918    pub const fn fr(val: f32) -> Self {
919        Self(CompactLength::fr(val))
920    }
921
922    /// A `calc()` value. The value passed here is treated as an opaque handle to
923    /// the actual calc representation and may be a pointer, index, etc.
924    ///
925    /// The low 3 bits are used as a tag value and will be returned as 0.
926    #[inline]
927    #[cfg(feature = "calc")]
928    pub fn calc(ptr: *const ()) -> Self {
929        Self(CompactLength::calc(ptr))
930    }
931
932    /// A `fit-content()` track sizing function whose limit is a `calc()` value. The value passed here is treated
933    /// as an opaque handle to the actual calc representation and may be a pointer, index, etc.
934    ///
935    /// The low 3 bits are used as a tag value and will be returned as 0.
936    #[inline]
937    #[cfg(feature = "calc")]
938    pub fn fit_content_calc(ptr: *const ()) -> Self {
939        Self(CompactLength::fit_content_calc(ptr))
940    }
941
942    /// Create a LengthPercentageAuto from a raw `CompactLength`.
943    /// # Safety
944    /// CompactLength must represent a valid variant for LengthPercentageAuto
945    #[allow(unsafe_code)]
946    pub unsafe fn from_raw(val: CompactLength) -> Self {
947        Self(val)
948    }
949
950    /// Get the underlying `CompactLength` representation of the value
951    pub fn into_raw(self) -> CompactLength {
952        self.0
953    }
954
955    /// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
956    #[inline(always)]
957    pub fn is_intrinsic(&self) -> bool {
958        self.0.is_intrinsic()
959    }
960
961    /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
962    /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
963    /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
964    #[inline(always)]
965    pub fn is_max_content_alike(&self) -> bool {
966        self.0.is_max_content_alike()
967    }
968
969    /// Returns true if the an Fr value, else false.
970    #[inline(always)]
971    pub fn is_fr(&self) -> bool {
972        self.0.is_fr()
973    }
974
975    /// Returns true if the is `Auto`, else false.
976    #[inline(always)]
977    pub fn is_auto(&self) -> bool {
978        self.0.is_auto()
979    }
980
981    /// Returns true if value is MinContent
982    #[inline(always)]
983    pub fn is_min_content(&self) -> bool {
984        self.0.is_min_content()
985    }
986
987    /// Returns true if value is MaxContent
988    #[inline(always)]
989    pub fn is_max_content(&self) -> bool {
990        self.0.is_max_content()
991    }
992
993    /// Returns true if value is FitContent(...)
994    #[inline(always)]
995    pub fn is_fit_content(&self) -> bool {
996        self.0.is_fit_content()
997    }
998
999    /// Returns true if value is MaxContent or FitContent(...)
1000    #[inline(always)]
1001    pub fn is_max_or_fit_content(&self) -> bool {
1002        self.0.is_max_or_fit_content()
1003    }
1004
1005    /// Returns whether the value can be resolved using `Self::definite_value`
1006    #[inline(always)]
1007    pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
1008        match self.0.tag() {
1009            CompactLength::LENGTH_TAG => true,
1010            CompactLength::PERCENT_TAG => parent_size.is_some(),
1011            #[cfg(feature = "calc")]
1012            _ if self.0.is_plain_calc() => parent_size.is_some(),
1013            _ => false,
1014        }
1015    }
1016
1017    /// Returns fixed point values directly. Attempts to resolve percentage values against
1018    /// the passed available_space and returns if this results in a concrete value (which it
1019    /// will if the available_space is `Some`). Otherwise returns None.
1020    #[inline(always)]
1021    pub fn definite_value(
1022        self,
1023        parent_size: Option<f32>,
1024        calc_resolver: impl Fn(*const (), f32) -> f32,
1025    ) -> Option<f32> {
1026        match self.0.tag() {
1027            CompactLength::LENGTH_TAG => Some(self.0.value()),
1028            CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1029            #[cfg(feature = "calc")]
1030            _ if self.0.is_plain_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1031            _ => None,
1032        }
1033    }
1034
1035    /// Resolve the `fit-content()` limit of this track sizing function against the passed `parent_size`.
1036    /// Returns `None` if this is not a `fit-content()` function, or if it has a percentage or `calc()`
1037    /// argument and `parent_size` is indefinite.
1038    #[inline(always)]
1039    pub fn fit_content_limit(
1040        self,
1041        parent_size: Option<f32>,
1042        calc_resolver: impl Fn(*const (), f32) -> f32,
1043    ) -> Option<f32> {
1044        #[cfg(not(feature = "calc"))]
1045        let _ = &calc_resolver;
1046        match self.0.tag() {
1047            CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
1048            CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1049            #[cfg(feature = "calc")]
1050            _ if self.0.is_fit_content_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1051            _ => None,
1052        }
1053    }
1054
1055    /// Resolve the maximum size of the track as defined by either:
1056    ///     - A fixed track sizing function
1057    ///     - A percentage track sizing function (with definite available space)
1058    ///     - A fit-content sizing function with fixed argument
1059    ///     - A fit-content sizing function with percentage argument (with definite available space)
1060    /// All other kinds of track sizing function return None.
1061    #[inline(always)]
1062    pub fn definite_limit(
1063        self,
1064        parent_size: Option<f32>,
1065        calc_resolver: impl Fn(*const (), f32) -> f32,
1066    ) -> Option<f32> {
1067        match self.0.tag() {
1068            CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
1069            CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1070            #[cfg(feature = "calc")]
1071            _ if self.0.is_fit_content_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1072            _ => self.definite_value(parent_size, calc_resolver),
1073        }
1074    }
1075
1076    /// Resolve percentage values against the passed parent_size, returning Some(value)
1077    /// Non-percentage values always return None.
1078    #[inline(always)]
1079    pub fn resolved_percentage_size(
1080        self,
1081        parent_size: f32,
1082        calc_resolver: impl Fn(*const (), f32) -> f32,
1083    ) -> Option<f32> {
1084        self.0.resolved_percentage_size(parent_size, calc_resolver)
1085    }
1086
1087    /// Whether the track sizing functions depends on the size of the parent node
1088    #[inline(always)]
1089    pub fn uses_percentage(self) -> bool {
1090        self.0.uses_percentage()
1091    }
1092
1093    /// Expand the compact representation into an [`ExpandedMaxTrackSizingFunction`] enum.
1094    ///
1095    /// This is useful when integrating with other libraries (e.g. for style inspection or
1096    /// serialization) as it allows the value to be pattern-matched without having to work
1097    /// with the raw [`CompactLength`] tagged-pointer representation directly.
1098    pub fn expand(self) -> ExpandedMaxTrackSizingFunction {
1099        match self.0.tag() {
1100            CompactLength::LENGTH_TAG => ExpandedMaxTrackSizingFunction::Length(self.0.value()),
1101            CompactLength::PERCENT_TAG => ExpandedMaxTrackSizingFunction::Percent(self.0.value()),
1102            CompactLength::AUTO_TAG => ExpandedMaxTrackSizingFunction::Auto,
1103            CompactLength::MIN_CONTENT_TAG => ExpandedMaxTrackSizingFunction::MinContent,
1104            CompactLength::MAX_CONTENT_TAG => ExpandedMaxTrackSizingFunction::MaxContent,
1105            CompactLength::FIT_CONTENT_PX_TAG => ExpandedMaxTrackSizingFunction::FitContentPx(self.0.value()),
1106            CompactLength::FIT_CONTENT_PERCENT_TAG => ExpandedMaxTrackSizingFunction::FitContentPercent(self.0.value()),
1107            #[cfg(feature = "calc")]
1108            _ if self.0.is_fit_content_calc() => ExpandedMaxTrackSizingFunction::FitContentCalc(self.0.calc_value()),
1109            CompactLength::FR_TAG => ExpandedMaxTrackSizingFunction::Fr(self.0.value()),
1110            #[cfg(feature = "calc")]
1111            _ if self.0.is_plain_calc() => ExpandedMaxTrackSizingFunction::Calc(self.0.calc_value()),
1112            _ => unreachable!("MaxTrackSizingFunction contains a value with an invalid tag"),
1113        }
1114    }
1115}
1116
1117/// The expanded, non-compact representation of a [`MaxTrackSizingFunction`].
1118///
1119/// Obtained via [`MaxTrackSizingFunction::expand`]. Can be converted back into a
1120/// [`MaxTrackSizingFunction`] using the [`From`] implementation.
1121#[derive(Copy, Clone, PartialEq, Debug)]
1122pub enum ExpandedMaxTrackSizingFunction {
1123    /// An absolute length (see [`MaxTrackSizingFunction::length`])
1124    Length(f32),
1125    /// A percentage length (see [`MaxTrackSizingFunction::percent`])
1126    Percent(f32),
1127    /// The automatic keyword (see [`MaxTrackSizingFunction::auto`])
1128    Auto,
1129    /// The `min-content` keyword (see [`MaxTrackSizingFunction::min_content`])
1130    MinContent,
1131    /// The `max-content` keyword (see [`MaxTrackSizingFunction::max_content`])
1132    MaxContent,
1133    /// A `fit-content(...)` value with a length limit (see [`MaxTrackSizingFunction::fit_content_px`])
1134    FitContentPx(f32),
1135    /// A `fit-content(...)` value with a percentage limit (see [`MaxTrackSizingFunction::fit_content_percent`])
1136    FitContentPercent(f32),
1137    /// Track maximum size should be content sized under a max-content constraint, but not larger than
1138    /// the `calc()` limit (an opaque handle to the calc representation)
1139    #[cfg(feature = "calc")]
1140    FitContentCalc(*const ()),
1141    /// A fraction of the leftover space (see [`MaxTrackSizingFunction::fr`])
1142    Fr(f32),
1143    /// A `calc()` value (see [`MaxTrackSizingFunction::calc`]). The pointer is an opaque handle to
1144    /// the calc representation, exactly as passed to the constructor.
1145    #[cfg(feature = "calc")]
1146    Calc(*const ()),
1147}
1148
1149impl From<MaxTrackSizingFunction> for ExpandedMaxTrackSizingFunction {
1150    fn from(value: MaxTrackSizingFunction) -> Self {
1151        value.expand()
1152    }
1153}
1154
1155impl From<ExpandedMaxTrackSizingFunction> for MaxTrackSizingFunction {
1156    fn from(value: ExpandedMaxTrackSizingFunction) -> Self {
1157        match value {
1158            ExpandedMaxTrackSizingFunction::Length(val) => Self::length(val),
1159            ExpandedMaxTrackSizingFunction::Percent(val) => Self::percent(val),
1160            ExpandedMaxTrackSizingFunction::Auto => Self::auto(),
1161            ExpandedMaxTrackSizingFunction::MinContent => Self::min_content(),
1162            ExpandedMaxTrackSizingFunction::MaxContent => Self::max_content(),
1163            ExpandedMaxTrackSizingFunction::FitContentPx(val) => Self::fit_content_px(val),
1164            ExpandedMaxTrackSizingFunction::FitContentPercent(val) => Self::fit_content_percent(val),
1165            #[cfg(feature = "calc")]
1166            ExpandedMaxTrackSizingFunction::FitContentCalc(ptr) => Self::fit_content_calc(ptr),
1167            ExpandedMaxTrackSizingFunction::Fr(val) => Self::fr(val),
1168            #[cfg(feature = "calc")]
1169            ExpandedMaxTrackSizingFunction::Calc(ptr) => Self::calc(ptr),
1170        }
1171    }
1172}
1173
1174/// Minimum track sizing function
1175///
1176/// Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
1177/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
1178/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
1179#[derive(Copy, Clone, PartialEq, Debug)]
1180#[cfg_attr(feature = "serde", derive(Serialize))]
1181pub struct MinTrackSizingFunction(pub(crate) CompactLength);
1182impl TaffyZero for MinTrackSizingFunction {
1183    const ZERO: Self = Self(CompactLength::ZERO);
1184}
1185impl TaffyAuto for MinTrackSizingFunction {
1186    const AUTO: Self = Self(CompactLength::AUTO);
1187}
1188impl TaffyMinContent for MinTrackSizingFunction {
1189    const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
1190}
1191impl TaffyMaxContent for MinTrackSizingFunction {
1192    const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
1193}
1194impl FromLength for MinTrackSizingFunction {
1195    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1196        Self::length(value.into() as f32)
1197    }
1198}
1199impl FromPercent for MinTrackSizingFunction {
1200    fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
1201        Self::percent(value.into() as f32)
1202    }
1203}
1204impl From<LengthPercentage> for MinTrackSizingFunction {
1205    fn from(input: LengthPercentage) -> Self {
1206        Self(input.0)
1207    }
1208}
1209impl From<LengthPercentageAuto> for MinTrackSizingFunction {
1210    fn from(input: LengthPercentageAuto) -> Self {
1211        Self(input.0)
1212    }
1213}
1214impl From<Dimension> for MinTrackSizingFunction {
1215    fn from(input: Dimension) -> Self {
1216        // Dimension supports values that are not valid min track sizing functions.
1217        // Map those to `auto`.
1218        match input.0.tag() {
1219            CompactLength::FIT_CONTENT_PX_TAG
1220            | CompactLength::FIT_CONTENT_PERCENT_TAG
1221            | CompactLength::FIT_CONTENT_KEYWORD_TAG
1222            | CompactLength::STRETCH_TAG
1223            | CompactLength::CONTENT_TAG => Self::auto(),
1224            _ if input.0.is_fit_content_calc() => Self::auto(),
1225            _ => Self(input.0),
1226        }
1227    }
1228}
1229
1230impl From<MaxTrackSizingFunction> for MinTrackSizingFunction {
1231    fn from(input: MaxTrackSizingFunction) -> Self {
1232        if input.is_fr() || input.is_fit_content() {
1233            return Self::auto();
1234        }
1235        Self(input.0)
1236    }
1237}
1238
1239#[cfg(feature = "parse")]
1240impl FromCss for MinTrackSizingFunction {
1241    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1242        let token = parser.next()?.clone();
1243        match token {
1244            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
1245            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
1246            Token::Ident(ref ident) => match ident.as_ref() {
1247                "auto" => Ok(Self::auto()),
1248                "min-content" => Ok(Self::min_content()),
1249                "max-content" => Ok(Self::max_content()),
1250                _ => Err(parser.new_unexpected_token_error(token))?,
1251            },
1252            token => Err(parser.new_unexpected_token_error(token))?,
1253        }
1254    }
1255}
1256
1257#[cfg(feature = "parse")]
1258from_str_from_css!(MinTrackSizingFunction);
1259
1260#[cfg(feature = "serde")]
1261impl<'de> serde::Deserialize<'de> for MinTrackSizingFunction {
1262    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1263    where
1264        D: serde::Deserializer<'de>,
1265    {
1266        let inner = CompactLength::deserialize(deserializer)?;
1267        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
1268        if matches!(
1269            inner.tag(),
1270            CompactLength::LENGTH_TAG
1271                | CompactLength::PERCENT_TAG
1272                | CompactLength::AUTO_TAG
1273                | CompactLength::MIN_CONTENT_TAG
1274                | CompactLength::MAX_CONTENT_TAG
1275                | CompactLength::FIT_CONTENT_PX_TAG
1276                | CompactLength::FIT_CONTENT_PERCENT_TAG
1277        ) {
1278            Ok(Self(inner))
1279        } else {
1280            Err(serde::de::Error::custom("Invalid tag"))
1281        }
1282    }
1283}
1284
1285impl MinTrackSizingFunction {
1286    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
1287    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
1288    #[inline(always)]
1289    pub const fn length(val: f32) -> Self {
1290        Self(CompactLength::length(val))
1291    }
1292
1293    /// A percentage length relative to the size of the containing block.
1294    ///
1295    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
1296    #[inline(always)]
1297    pub const fn percent(val: f32) -> Self {
1298        Self(CompactLength::percent(val))
1299    }
1300
1301    /// The dimension should be automatically computed according to algorithm-specific rules
1302    /// regarding the default size of boxes.
1303    #[inline(always)]
1304    pub const fn auto() -> Self {
1305        Self(CompactLength::auto())
1306    }
1307
1308    /// The size should be the "min-content" size.
1309    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
1310    #[inline(always)]
1311    pub const fn min_content() -> Self {
1312        Self(CompactLength::min_content())
1313    }
1314
1315    /// The size should be the "max-content" size.
1316    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
1317    #[inline(always)]
1318    pub const fn max_content() -> Self {
1319        Self(CompactLength::max_content())
1320    }
1321
1322    /// A `calc()` value. The value passed here is treated as an opaque handle to
1323    /// the actual calc representation and may be a pointer, index, etc.
1324    ///
1325    /// The low 3 bits are used as a tag value and will be returned as 0.
1326    #[inline]
1327    #[cfg(feature = "calc")]
1328    pub fn calc(ptr: *const ()) -> Self {
1329        Self(CompactLength::calc(ptr))
1330    }
1331
1332    /// Create a LengthPercentageAuto from a raw `CompactLength`.
1333    /// # Safety
1334    /// CompactLength must represent a valid variant for LengthPercentageAuto
1335    #[allow(unsafe_code)]
1336    pub unsafe fn from_raw(val: CompactLength) -> Self {
1337        Self(val)
1338    }
1339
1340    /// Get the underlying `CompactLength` representation of the value
1341    pub fn into_raw(self) -> CompactLength {
1342        self.0
1343    }
1344
1345    /// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
1346    #[inline(always)]
1347    pub fn is_intrinsic(&self) -> bool {
1348        self.0.is_intrinsic()
1349    }
1350
1351    /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
1352    #[inline(always)]
1353    pub fn is_min_or_max_content(&self) -> bool {
1354        self.0.is_min_or_max_content()
1355    }
1356
1357    /// Returns true if the value is an fr value
1358    #[inline(always)]
1359    pub fn is_fr(&self) -> bool {
1360        self.0.is_fr()
1361    }
1362
1363    /// Returns true if the is `Auto`, else false.
1364    #[inline(always)]
1365    pub fn is_auto(&self) -> bool {
1366        self.0.is_auto()
1367    }
1368
1369    /// Returns true if the value is `Auto`, or is a percentage and `parent_size` is `None`
1370    #[inline(always)]
1371    pub fn behaves_as_auto(&self, parent_size: Option<f32>) -> bool {
1372        self.0.is_auto() || (parent_size.is_none() && self.0.uses_percentage())
1373    }
1374
1375    /// Returns true if value is MinContent
1376    #[inline(always)]
1377    pub fn is_min_content(&self) -> bool {
1378        self.0.is_min_content()
1379    }
1380
1381    /// Returns true if value is MaxContent
1382    #[inline(always)]
1383    pub fn is_max_content(&self) -> bool {
1384        self.0.is_max_content()
1385    }
1386
1387    /// Returns fixed point values directly. Attempts to resolve percentage values against
1388    /// the passed available_space and returns if this results in a concrete value (which it
1389    /// will if the available_space is `Some`). Otherwise returns `None`.
1390    #[inline(always)]
1391    pub fn definite_value(
1392        self,
1393        parent_size: Option<f32>,
1394        calc_resolver: impl Fn(*const (), f32) -> f32,
1395    ) -> Option<f32> {
1396        match self.0.tag() {
1397            CompactLength::LENGTH_TAG => Some(self.0.value()),
1398            CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
1399            #[cfg(feature = "calc")]
1400            _ if self.0.is_plain_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
1401            _ => None,
1402        }
1403    }
1404
1405    /// Resolve percentage values against the passed parent_size, returning Some(value)
1406    /// Non-percentage values always return None.
1407    #[inline(always)]
1408    pub fn resolved_percentage_size(
1409        self,
1410        parent_size: f32,
1411        calc_resolver: impl Fn(*const (), f32) -> f32,
1412    ) -> Option<f32> {
1413        self.0.resolved_percentage_size(parent_size, calc_resolver)
1414    }
1415
1416    /// Whether the track sizing functions depends on the size of the parent node
1417    #[inline(always)]
1418    pub fn uses_percentage(self) -> bool {
1419        self.0.uses_percentage()
1420    }
1421
1422    /// Expand the compact representation into an [`ExpandedMinTrackSizingFunction`] enum.
1423    ///
1424    /// This is useful when integrating with other libraries (e.g. for style inspection or
1425    /// serialization) as it allows the value to be pattern-matched without having to work
1426    /// with the raw [`CompactLength`] tagged-pointer representation directly.
1427    pub fn expand(self) -> ExpandedMinTrackSizingFunction {
1428        match self.0.tag() {
1429            CompactLength::LENGTH_TAG => ExpandedMinTrackSizingFunction::Length(self.0.value()),
1430            CompactLength::PERCENT_TAG => ExpandedMinTrackSizingFunction::Percent(self.0.value()),
1431            CompactLength::AUTO_TAG => ExpandedMinTrackSizingFunction::Auto,
1432            CompactLength::MIN_CONTENT_TAG => ExpandedMinTrackSizingFunction::MinContent,
1433            CompactLength::MAX_CONTENT_TAG => ExpandedMinTrackSizingFunction::MaxContent,
1434            #[cfg(feature = "calc")]
1435            _ if self.0.is_plain_calc() => ExpandedMinTrackSizingFunction::Calc(self.0.calc_value()),
1436            _ => unreachable!("MinTrackSizingFunction contains a value with an invalid tag"),
1437        }
1438    }
1439}
1440
1441/// The expanded, non-compact representation of a [`MinTrackSizingFunction`].
1442///
1443/// Obtained via [`MinTrackSizingFunction::expand`]. Can be converted back into a
1444/// [`MinTrackSizingFunction`] using the [`From`] implementation.
1445#[derive(Copy, Clone, PartialEq, Debug)]
1446pub enum ExpandedMinTrackSizingFunction {
1447    /// An absolute length (see [`MinTrackSizingFunction::length`])
1448    Length(f32),
1449    /// A percentage length (see [`MinTrackSizingFunction::percent`])
1450    Percent(f32),
1451    /// The automatic keyword (see [`MinTrackSizingFunction::auto`])
1452    Auto,
1453    /// The `min-content` keyword (see [`MinTrackSizingFunction::min_content`])
1454    MinContent,
1455    /// The `max-content` keyword (see [`MinTrackSizingFunction::max_content`])
1456    MaxContent,
1457    /// A `calc()` value (see [`MinTrackSizingFunction::calc`]). The pointer is an opaque handle to
1458    /// the calc representation, exactly as passed to the constructor.
1459    #[cfg(feature = "calc")]
1460    Calc(*const ()),
1461}
1462
1463impl From<MinTrackSizingFunction> for ExpandedMinTrackSizingFunction {
1464    fn from(value: MinTrackSizingFunction) -> Self {
1465        value.expand()
1466    }
1467}
1468
1469impl From<ExpandedMinTrackSizingFunction> for MinTrackSizingFunction {
1470    fn from(value: ExpandedMinTrackSizingFunction) -> Self {
1471        match value {
1472            ExpandedMinTrackSizingFunction::Length(val) => Self::length(val),
1473            ExpandedMinTrackSizingFunction::Percent(val) => Self::percent(val),
1474            ExpandedMinTrackSizingFunction::Auto => Self::auto(),
1475            ExpandedMinTrackSizingFunction::MinContent => Self::min_content(),
1476            ExpandedMinTrackSizingFunction::MaxContent => Self::max_content(),
1477            #[cfg(feature = "calc")]
1478            ExpandedMinTrackSizingFunction::Calc(ptr) => Self::calc(ptr),
1479        }
1480    }
1481}
1482
1483/// The sizing function for a grid track (row/column)
1484///
1485/// May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
1486/// or a scalar value which applies to both track sizing functions.
1487pub type TrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
1488impl TrackSizingFunction {
1489    /// Extract the min track sizing function
1490    pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
1491        self.min
1492    }
1493    /// Extract the max track sizing function
1494    pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
1495        self.max
1496    }
1497    /// Determine whether at least one of the components ("min" and "max") are fixed sizing function
1498    ///
1499    /// A fixed sizing function is a `<length-percentage>`, which includes `calc()` values (and so
1500    /// `min()`, `max()` and `clamp()`) such as `minmax(min(100%, 300px), 1fr)`.
1501    /// <https://www.w3.org/TR/css-grid-1/#fixed-sizing-function>
1502    pub fn has_fixed_component(&self) -> bool {
1503        self.min.0.is_length_percentage_or_calc() || self.max.0.is_length_percentage_or_calc()
1504    }
1505}
1506impl TaffyAuto for TrackSizingFunction {
1507    const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
1508}
1509impl TaffyMinContent for TrackSizingFunction {
1510    const MIN_CONTENT: Self =
1511        Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
1512}
1513impl TaffyMaxContent for TrackSizingFunction {
1514    const MAX_CONTENT: Self =
1515        Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
1516}
1517impl TaffyFitContent for TrackSizingFunction {
1518    fn fit_content(argument: LengthPercentage) -> Self {
1519        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::fit_content(argument) }
1520    }
1521}
1522impl TaffyZero for TrackSizingFunction {
1523    const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
1524}
1525impl FromLength for TrackSizingFunction {
1526    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1527        Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
1528    }
1529}
1530impl FromPercent for TrackSizingFunction {
1531    fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1532        Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
1533    }
1534}
1535impl FromFr for TrackSizingFunction {
1536    fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1537        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
1538    }
1539}
1540impl From<LengthPercentage> for TrackSizingFunction {
1541    fn from(input: LengthPercentage) -> Self {
1542        Self { min: input.into(), max: input.into() }
1543    }
1544}
1545impl From<LengthPercentageAuto> for TrackSizingFunction {
1546    fn from(input: LengthPercentageAuto) -> Self {
1547        Self { min: input.into(), max: input.into() }
1548    }
1549}
1550impl From<Dimension> for TrackSizingFunction {
1551    fn from(input: Dimension) -> Self {
1552        Self { min: input.into(), max: input.into() }
1553    }
1554}
1555
1556#[cfg(feature = "parse")]
1557impl FromCss for TrackSizingFunction {
1558    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1559        // Try to parse a minmax() function
1560        if let Ok(value) = parser.try_parse(|parser| {
1561            parser.expect_function_matching("minmax")?;
1562            parser.parse_nested_block(|parser| {
1563                let min = MinTrackSizingFunction::from_css(parser)?;
1564                parser.expect_comma()?;
1565                let max = MaxTrackSizingFunction::from_css(parser)?;
1566
1567                Ok(Self { min, max })
1568            })
1569        }) {
1570            return Ok(value);
1571        }
1572
1573        // Else parse a max track sizing function
1574        let max = MaxTrackSizingFunction::from_css(parser)?;
1575        let min = max.into();
1576        Ok(Self { min, max })
1577    }
1578}
1579
1580#[cfg(feature = "parse")]
1581from_str_from_css!(TrackSizingFunction);
1582
1583/// The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
1584///
1585/// See <https://www.w3.org/TR/css-grid-1/#auto-repeat> for an explanation of how auto-repeated track definitions work
1586/// and the difference between AutoFit and AutoFill.
1587#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1588#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1589pub enum RepetitionCount {
1590    /// Auto-repeating tracks should be generated to fit the container
1591    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>
1592    AutoFill,
1593    /// Auto-repeating tracks should be generated to fit the container
1594    /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>
1595    AutoFit,
1596    /// The specified tracks should be repeated exacts N times
1597    Count(u16),
1598}
1599impl From<u16> for RepetitionCount {
1600    fn from(value: u16) -> Self {
1601        Self::Count(value)
1602    }
1603}
1604
1605/// Error returned when trying to convert a string to a GridTrackRepetition and that string is not
1606/// either "auto-fit" or "auto-fill"
1607#[derive(Debug)]
1608pub struct InvalidStringRepetitionValue;
1609#[cfg(feature = "std")]
1610impl std::error::Error for InvalidStringRepetitionValue {}
1611impl core::fmt::Display for InvalidStringRepetitionValue {
1612    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1613        f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
1614    }
1615}
1616impl TryFrom<&str> for RepetitionCount {
1617    type Error = InvalidStringRepetitionValue;
1618    fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
1619        match value {
1620            "auto-fit" => Ok(Self::AutoFit),
1621            "auto-fill" => Ok(Self::AutoFill),
1622            _ => Err(InvalidStringRepetitionValue),
1623        }
1624    }
1625}
1626
1627#[cfg(feature = "parse")]
1628impl FromCss for RepetitionCount {
1629    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1630        match parser.next()?.clone() {
1631            Token::Number { int_value: Some(value), .. } if value.is_positive() => {
1632                Ok(Self::Count(saturating_u16(value)))
1633            }
1634            Token::Ident(ident) if ident == "auto-fit" => Ok(Self::AutoFit),
1635            Token::Ident(ident) if ident == "auto-fill" => Ok(Self::AutoFill),
1636            token => Err(parser.new_unexpected_token_error(token))?,
1637        }
1638    }
1639}
1640#[cfg(feature = "parse")]
1641from_str_from_css!(RepetitionCount);
1642
1643/// A typed representation of a `repeat(..)` in `grid-template-*` value
1644#[derive(Clone, PartialEq, Debug)]
1645#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1646pub struct GridTemplateRepetition<S: CheapCloneStr> {
1647    /// The number of the times the repeat is repeated
1648    pub count: RepetitionCount,
1649    /// The tracks to repeat
1650    pub tracks: Vec<TrackSizingFunction>,
1651    /// The line names for the repeated tracks
1652    ///
1653    /// Line name sets are positional: set `i` names the `i`th line of each repetition. This must
1654    /// either be empty (all lines are unnamed) or contain exactly `tracks.len() + 1` sets (one set
1655    /// per line, including both edge lines; sets may be empty). Other lengths panic during layout.
1656    pub line_names: Vec<Vec<S>>,
1657}
1658
1659#[rustfmt::skip]
1660impl<S: CheapCloneStr> GenericRepetition for &'_ GridTemplateRepetition<S> {
1661    type CustomIdent = S;
1662    type RepetitionTrackList<'a> = core::iter::Copied<core::slice::Iter<'a, TrackSizingFunction>> where Self: 'a;
1663    type TemplateLineNames<'a> = core::iter::Map<core::slice::Iter<'a, Vec<S>>, fn(&Vec<S>) -> core::slice::Iter<'_, S>> where Self: 'a;
1664    #[inline(always)]
1665    fn count(&self) -> RepetitionCount {
1666        self.count
1667    }
1668    #[inline(always)]
1669    fn track_count(&self) -> u16 {
1670        self.tracks.len().min(u16::MAX as usize) as u16
1671    }
1672    #[inline(always)]
1673    fn tracks(&self) -> Self::RepetitionTrackList<'_> {
1674        self.tracks.iter().copied()
1675    }
1676    #[inline(always)]
1677    fn lines_names(&self) -> Self::TemplateLineNames<'_> {
1678        self.line_names.iter().map(|names| names.iter())
1679    }
1680}
1681
1682/// An element in a `grid-template-columns` or `grid-template-rows` definition.
1683/// Either a track sizing function or a repeat().
1684///
1685/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
1686#[derive(Clone, PartialEq, Debug)]
1687#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1688pub enum GridTemplateComponent<S: CheapCloneStr> {
1689    /// A single non-repeated track
1690    Single(TrackSizingFunction),
1691    /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
1692    /// Only valid if every track in template (not just the repetition) has a fixed size.
1693    Repeat(GridTemplateRepetition<S>),
1694}
1695
1696impl<S: CheapCloneStr> GridTemplateComponent<S> {
1697    /// Convert a `GridTemplateComponent` into a `GridTemplateComponentRef`
1698    pub fn as_component_ref(&self) -> GenericGridTemplateComponent<S, &GridTemplateRepetition<S>> {
1699        match self {
1700            GridTemplateComponent::Single(size) => GenericGridTemplateComponent::Single(*size),
1701            GridTemplateComponent::Repeat(repetition) => GenericGridTemplateComponent::Repeat(repetition),
1702        }
1703    }
1704}
1705
1706impl<S: CheapCloneStr> GridTemplateComponent<S> {
1707    /// Whether the track definition is a auto-repeated fragment
1708    pub fn is_auto_repetition(&self) -> bool {
1709        matches!(
1710            self,
1711            Self::Repeat(GridTemplateRepetition { count: RepetitionCount::AutoFit | RepetitionCount::AutoFill, .. })
1712        )
1713    }
1714}
1715impl<S: CheapCloneStr> TaffyAuto for GridTemplateComponent<S> {
1716    const AUTO: Self = Self::Single(TrackSizingFunction::AUTO);
1717}
1718impl<S: CheapCloneStr> TaffyMinContent for GridTemplateComponent<S> {
1719    const MIN_CONTENT: Self = Self::Single(TrackSizingFunction::MIN_CONTENT);
1720}
1721impl<S: CheapCloneStr> TaffyMaxContent for GridTemplateComponent<S> {
1722    const MAX_CONTENT: Self = Self::Single(TrackSizingFunction::MAX_CONTENT);
1723}
1724impl<S: CheapCloneStr> TaffyFitContent for GridTemplateComponent<S> {
1725    fn fit_content(argument: LengthPercentage) -> Self {
1726        Self::Single(TrackSizingFunction::fit_content(argument))
1727    }
1728}
1729impl<S: CheapCloneStr> TaffyZero for GridTemplateComponent<S> {
1730    const ZERO: Self = Self::Single(TrackSizingFunction::ZERO);
1731}
1732impl<S: CheapCloneStr> FromLength for GridTemplateComponent<S> {
1733    fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
1734        Self::Single(TrackSizingFunction::from_length(value))
1735    }
1736}
1737impl<S: CheapCloneStr> FromPercent for GridTemplateComponent<S> {
1738    fn from_percent<Input: Into<f64> + Copy>(percent: Input) -> Self {
1739        Self::Single(TrackSizingFunction::from_percent(percent))
1740    }
1741}
1742impl<S: CheapCloneStr> FromFr for GridTemplateComponent<S> {
1743    fn from_fr<Input: Into<f64> + Copy>(flex: Input) -> Self {
1744        Self::Single(TrackSizingFunction::from_fr(flex))
1745    }
1746}
1747impl<S: CheapCloneStr> From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for GridTemplateComponent<S> {
1748    fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
1749        Self::Single(input)
1750    }
1751}
1752
1753#[cfg(feature = "parse")]
1754impl<S: CheapCloneStr> FromCss for GridTemplateComponent<S> {
1755    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1756        // Try to parse a minmax() function
1757        if let Ok(value) = parser.try_parse(|parser| {
1758            parser.expect_function_matching("repeat")?;
1759            parser.parse_nested_block(|parser| {
1760                let count = RepetitionCount::from_css(parser)?;
1761                parser.expect_comma()?;
1762                let tracks = GridTemplateTracks::<S, TrackSizingFunction>::from_css(parser)?;
1763
1764                Ok(Self::Repeat(GridTemplateRepetition { count, tracks: tracks.tracks, line_names: tracks.line_names }))
1765            })
1766        }) {
1767            return Ok(value);
1768        }
1769
1770        // Else parse a track sizing function
1771        let track_sizing_function = TrackSizingFunction::from_css(parser)?;
1772        Ok(Self::Single(track_sizing_function))
1773    }
1774}
1775#[cfg(feature = "parse")]
1776impl<S: CheapCloneStr> core::str::FromStr for GridTemplateComponent<S> {
1777    type Err = ParseError;
1778    fn from_str(input: &str) -> Result<Self, Self::Err> {
1779        parse_css_str_entirely(input)
1780    }
1781}
1782
1783#[derive(Clone, PartialEq, Debug)]
1784#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1785#[doc(hidden)]
1786pub struct GridTemplateTracks<S: CheapCloneStr, Track> {
1787    /// The tracks to repeat
1788    pub tracks: Vec<Track>,
1789    /// The line names for the repeated tracks
1790    pub line_names: Vec<Vec<S>>,
1791}
1792
1793impl<S: CheapCloneStr, Track> Default for GridTemplateTracks<S, Track> {
1794    fn default() -> Self {
1795        Self { tracks: Vec::new(), line_names: Vec::new() }
1796    }
1797}
1798
1799#[cfg(feature = "parse")]
1800impl<S: CheapCloneStr, Track: FromCss + Debug> FromCss for GridTemplateTracks<S, Track> {
1801    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1802        fn try_parse_line_names<'i, S: CheapCloneStr>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Vec<S>> {
1803            parser.try_parse(|parser| {
1804                parser.expect_square_bracket_block()?;
1805                parser.parse_nested_block(|parser| {
1806                    let mut line_names = Vec::new();
1807                    while !parser.is_exhausted() {
1808                        line_names.push(S::from(parser.expect_ident_cloned()?.as_ref()));
1809                    }
1810                    Ok(line_names)
1811                })
1812            })
1813        }
1814
1815        // Line name groups are positional (group `i` names line `i`), so a group is pushed for
1816        // every line, empty when the line has no `[...]` in the source
1817        let mut tracks = Self::default();
1818        tracks.line_names.push(try_parse_line_names(parser).unwrap_or_default());
1819
1820        while !parser.is_exhausted() {
1821            tracks.tracks.push(Track::from_css(parser)?);
1822            tracks.line_names.push(try_parse_line_names(parser).unwrap_or_default());
1823        }
1824
1825        if tracks.tracks.is_empty() {
1826            return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1827        }
1828
1829        Ok(tracks)
1830    }
1831}
1832#[cfg(feature = "parse")]
1833impl<S: CheapCloneStr, Track: FromCss + Debug> core::str::FromStr for GridTemplateTracks<S, Track> {
1834    type Err = ParseError;
1835    fn from_str(input: &str) -> Result<Self, Self::Err> {
1836        parse_css_str_entirely(input)
1837    }
1838}
1839
1840#[derive(Default)]
1841#[doc(hidden)]
1842pub struct GridAutoTracks(pub Vec<TrackSizingFunction>);
1843
1844#[cfg(feature = "parse")]
1845impl FromCss for GridAutoTracks {
1846    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
1847        let mut tracks = Self::default();
1848        while !parser.is_exhausted() {
1849            tracks.0.push(TrackSizingFunction::from_css(parser)?);
1850        }
1851        if tracks.0.is_empty() {
1852            return Err(parser.new_error(cssparser::BasicParseErrorKind::EndOfInput));
1853        }
1854        Ok(tracks)
1855    }
1856}
1857#[cfg(feature = "parse")]
1858from_str_from_css!(GridAutoTracks);
1859
1860#[cfg(all(test, feature = "parse"))]
1861mod tests {
1862    use super::*;
1863    use crate::sys::DefaultCheapStr;
1864
1865    #[test]
1866    fn grid_placement_parser_saturates_numeric_values() {
1867        assert_eq!(
1868            "32768".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1869            GridPlacement::Line(GridLine::from(i16::MAX))
1870        );
1871        assert_eq!(
1872            "-32769".parse::<GridPlacement<DefaultCheapStr>>().unwrap(),
1873            GridPlacement::Line(GridLine::from(i16::MIN))
1874        );
1875        assert_eq!("span 65536".parse::<GridPlacement<DefaultCheapStr>>().unwrap(), GridPlacement::Span(u16::MAX));
1876
1877        let named_line = "32768 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1878        assert!(matches!(named_line, GridPlacement::NamedLine(_, i16::MAX)));
1879
1880        let named_span = "span 65536 line".parse::<GridPlacement<DefaultCheapStr>>().unwrap();
1881        assert!(matches!(named_span, GridPlacement::NamedSpan(_, u16::MAX)));
1882    }
1883
1884    #[test]
1885    fn repetition_parser_saturates_numeric_values() {
1886        assert_eq!("65536".parse::<RepetitionCount>().unwrap(), RepetitionCount::Count(u16::MAX));
1887
1888        let component = "repeat(65536, 1px)".parse::<GridTemplateComponent<DefaultCheapStr>>().unwrap();
1889        assert!(matches!(
1890            component,
1891            GridTemplateComponent::Repeat(GridTemplateRepetition { count: RepetitionCount::Count(u16::MAX), .. })
1892        ));
1893    }
1894
1895    #[test]
1896    fn repetition_track_count_saturates() {
1897        let repetition = GridTemplateRepetition::<DefaultCheapStr> {
1898            count: RepetitionCount::Count(1),
1899            tracks: vec![TrackSizingFunction::AUTO; u16::MAX as usize + 1],
1900            line_names: Vec::new(),
1901        };
1902        assert_eq!((&repetition).track_count(), u16::MAX);
1903    }
1904}
1905
1906#[cfg(test)]
1907mod expand_tests {
1908    use super::*;
1909
1910    #[test]
1911    fn max_track_sizing_function_round_trips() {
1912        let cases = [
1913            MaxTrackSizingFunction::length(12.0),
1914            MaxTrackSizingFunction::percent(0.5),
1915            MaxTrackSizingFunction::auto(),
1916            MaxTrackSizingFunction::min_content(),
1917            MaxTrackSizingFunction::max_content(),
1918            MaxTrackSizingFunction::fit_content_px(30.0),
1919            MaxTrackSizingFunction::fit_content_percent(0.75),
1920            MaxTrackSizingFunction::fr(2.0),
1921        ];
1922        for value in cases {
1923            assert_eq!(MaxTrackSizingFunction::from(value.expand()), value);
1924            assert_eq!(ExpandedMaxTrackSizingFunction::from(value), value.expand());
1925        }
1926        assert_eq!(MaxTrackSizingFunction::fr(2.0).expand(), ExpandedMaxTrackSizingFunction::Fr(2.0));
1927        assert_eq!(
1928            MaxTrackSizingFunction::fit_content_px(30.0).expand(),
1929            ExpandedMaxTrackSizingFunction::FitContentPx(30.0)
1930        );
1931    }
1932
1933    #[test]
1934    fn min_track_sizing_function_round_trips() {
1935        let cases = [
1936            MinTrackSizingFunction::length(12.0),
1937            MinTrackSizingFunction::percent(0.5),
1938            MinTrackSizingFunction::auto(),
1939            MinTrackSizingFunction::min_content(),
1940            MinTrackSizingFunction::max_content(),
1941        ];
1942        for value in cases {
1943            assert_eq!(MinTrackSizingFunction::from(value.expand()), value);
1944            assert_eq!(ExpandedMinTrackSizingFunction::from(value), value.expand());
1945        }
1946        assert_eq!(MinTrackSizingFunction::max_content().expand(), ExpandedMinTrackSizingFunction::MaxContent);
1947    }
1948
1949    #[cfg(feature = "calc")]
1950    #[test]
1951    fn track_sizing_function_calc_round_trips() {
1952        #[allow(dead_code)]
1953        #[repr(align(8))]
1954        struct Aligned(u64);
1955        static HANDLE: Aligned = Aligned(0);
1956        let handle = &HANDLE as *const Aligned as *const ();
1957
1958        assert_eq!(MaxTrackSizingFunction::calc(handle).expand(), ExpandedMaxTrackSizingFunction::Calc(handle));
1959        assert_eq!(
1960            MaxTrackSizingFunction::fit_content_calc(handle).expand(),
1961            ExpandedMaxTrackSizingFunction::FitContentCalc(handle)
1962        );
1963        assert_eq!(
1964            MaxTrackSizingFunction::from(ExpandedMaxTrackSizingFunction::FitContentCalc(handle)),
1965            MaxTrackSizingFunction::fit_content_calc(handle)
1966        );
1967        assert_eq!(
1968            MaxTrackSizingFunction::fit_content(LengthPercentage::calc(handle)),
1969            MaxTrackSizingFunction::fit_content_calc(handle)
1970        );
1971        assert_eq!(
1972            MinTrackSizingFunction::from(MaxTrackSizingFunction::fit_content_calc(handle)),
1973            MinTrackSizingFunction::AUTO
1974        );
1975        assert_eq!(MinTrackSizingFunction::from(Dimension::fit_content_calc(handle)), MinTrackSizingFunction::AUTO);
1976        assert_eq!(
1977            MaxTrackSizingFunction::from(ExpandedMaxTrackSizingFunction::Calc(handle)),
1978            MaxTrackSizingFunction::calc(handle)
1979        );
1980        assert_eq!(MinTrackSizingFunction::calc(handle).expand(), ExpandedMinTrackSizingFunction::Calc(handle));
1981    }
1982}