style/values/generics/
grid.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Generic types for the handling of
6//! [grids](https://drafts.csswg.org/css-grid/).
7
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::values::specified;
11use crate::values::{CSSFloat, CustomIdent};
12use crate::{One, Zero};
13use cssparser::Parser;
14use std::fmt::{self, Write};
15use std::{cmp, usize};
16use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
17
18/// These are the limits that we choose to clamp grid line numbers to.
19/// http://drafts.csswg.org/css-grid/#overlarge-grids
20/// line_num is clamped to this range at parse time.
21pub const MIN_GRID_LINE: i32 = -10000;
22/// See above.
23pub const MAX_GRID_LINE: i32 = 10000;
24
25/// A `<grid-line>` type.
26///
27/// <https://drafts.csswg.org/css-grid/#typedef-grid-row-start-grid-line>
28#[derive(
29    Clone,
30    Debug,
31    Default,
32    MallocSizeOf,
33    PartialEq,
34    SpecifiedValueInfo,
35    ToComputedValue,
36    ToResolvedValue,
37    ToShmem,
38    ToTyped,
39)]
40#[repr(C)]
41pub struct GenericGridLine<Integer> {
42    /// A custom identifier for named lines, or the empty atom otherwise.
43    ///
44    /// <https://drafts.csswg.org/css-grid/#grid-placement-slot>
45    pub ident: CustomIdent,
46    /// Denotes the nth grid line from grid item's placement.
47    ///
48    /// This is clamped by MIN_GRID_LINE and MAX_GRID_LINE.
49    ///
50    /// NOTE(emilio): If we ever allow animating these we need to either do
51    /// something more complicated for the clamping, or do this clamping at
52    /// used-value time.
53    pub line_num: Integer,
54    /// Flag to check whether it's a `span` keyword.
55    pub is_span: bool,
56}
57
58pub use self::GenericGridLine as GridLine;
59
60impl<Integer> GridLine<Integer>
61where
62    Integer: PartialEq + Zero,
63{
64    /// The `auto` value.
65    pub fn auto() -> Self {
66        Self {
67            is_span: false,
68            line_num: Zero::zero(),
69            ident: CustomIdent(atom!("")),
70        }
71    }
72
73    /// Check whether this `<grid-line>` represents an `auto` value.
74    pub fn is_auto(&self) -> bool {
75        self.ident.0 == atom!("") && self.line_num.is_zero() && !self.is_span
76    }
77
78    /// Check whether this `<grid-line>` represents a `<custom-ident>` value.
79    pub fn is_ident_only(&self) -> bool {
80        self.ident.0 != atom!("") && self.line_num.is_zero() && !self.is_span
81    }
82
83    /// Check if `self` makes `other` omittable according to the rules at:
84    /// https://drafts.csswg.org/css-grid/#propdef-grid-column
85    /// https://drafts.csswg.org/css-grid/#propdef-grid-area
86    pub fn can_omit(&self, other: &Self) -> bool {
87        if self.is_ident_only() {
88            self == other
89        } else {
90            other.is_auto()
91        }
92    }
93}
94
95impl<Integer> ToCss for GridLine<Integer>
96where
97    Integer: ToCss + PartialEq + Zero + One,
98{
99    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
100    where
101        W: Write,
102    {
103        // 1. `auto`
104        if self.is_auto() {
105            return dest.write_str("auto");
106        }
107
108        // 2. `<custom-ident>`
109        if self.is_ident_only() {
110            return self.ident.to_css(dest);
111        }
112
113        // 3. `[ span && [ <integer [1,∞]> || <custom-ident> ] ]`
114        let has_ident = self.ident.0 != atom!("");
115        if self.is_span {
116            dest.write_str("span")?;
117            debug_assert!(!self.line_num.is_zero() || has_ident);
118
119            // We omit `line_num` if
120            // 1. we don't specify it, or
121            // 2. it is the default value, i.e. 1.0, and the ident is specified.
122            // https://drafts.csswg.org/css-grid/#grid-placement-span-int
123            if !self.line_num.is_zero() && !(self.line_num.is_one() && has_ident) {
124                dest.write_char(' ')?;
125                self.line_num.to_css(dest)?;
126            }
127
128            if has_ident {
129                dest.write_char(' ')?;
130                self.ident.to_css(dest)?;
131            }
132            return Ok(());
133        }
134
135        // 4. `[ <integer [-∞,-1]> | <integer [1,∞]> ] && <custom-ident>? ]`
136        debug_assert!(!self.line_num.is_zero());
137        self.line_num.to_css(dest)?;
138        if has_ident {
139            dest.write_char(' ')?;
140            self.ident.to_css(dest)?;
141        }
142        Ok(())
143    }
144}
145
146impl Parse for GridLine<specified::Integer> {
147    fn parse<'i, 't>(
148        context: &ParserContext,
149        input: &mut Parser<'i, 't>,
150    ) -> Result<Self, ParseError<'i>> {
151        let mut grid_line = Self::auto();
152        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
153            return Ok(grid_line);
154        }
155
156        // <custom-ident> | [ <integer> && <custom-ident>? ] | [ span && [ <integer> || <custom-ident> ] ]
157        // This <grid-line> horror is simply,
158        // [ span? && [ <custom-ident> || <integer> ] ]
159        // And, for some magical reason, "span" should be the first or last value and not in-between.
160        let mut val_before_span = false;
161
162        for _ in 0..3 {
163            // Maximum possible entities for <grid-line>
164            let location = input.current_source_location();
165            if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() {
166                if grid_line.is_span {
167                    return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
168                }
169
170                if !grid_line.line_num.is_zero() || grid_line.ident.0 != atom!("") {
171                    val_before_span = true;
172                }
173
174                grid_line.is_span = true;
175            } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) {
176                // FIXME(emilio): Probably shouldn't reject if it's calc()...
177                let value = i.value();
178                if value == 0 || val_before_span || !grid_line.line_num.is_zero() {
179                    return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
180                }
181
182                grid_line.line_num = specified::Integer::new(cmp::max(
183                    MIN_GRID_LINE,
184                    cmp::min(value, MAX_GRID_LINE),
185                ));
186            } else if let Ok(name) = input.try_parse(|i| CustomIdent::parse(i, &["auto"])) {
187                if val_before_span || grid_line.ident.0 != atom!("") {
188                    return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
189                }
190                // NOTE(emilio): `span` is consumed above, so we only need to
191                // reject `auto`.
192                grid_line.ident = name;
193            } else {
194                break;
195            }
196        }
197
198        if grid_line.is_auto() {
199            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
200        }
201
202        if grid_line.is_span {
203            if !grid_line.line_num.is_zero() {
204                if grid_line.line_num.value() <= 0 {
205                    // disallow negative integers for grid spans
206                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
207                }
208            } else if grid_line.ident.0 == atom!("") {
209                // integer could be omitted
210                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
211            }
212        }
213
214        Ok(grid_line)
215    }
216}
217
218/// A track breadth for explicit grid track sizing. It's generic solely to
219/// avoid re-implementing it for the computed type.
220///
221/// <https://drafts.csswg.org/css-grid/#typedef-track-breadth>
222#[derive(
223    Animate,
224    Clone,
225    Debug,
226    MallocSizeOf,
227    PartialEq,
228    SpecifiedValueInfo,
229    ToAnimatedValue,
230    ToComputedValue,
231    ToCss,
232    ToResolvedValue,
233    ToShmem,
234)]
235#[repr(C, u8)]
236pub enum GenericTrackBreadth<L> {
237    /// The generic type is almost always a non-negative `<length-percentage>`
238    Breadth(L),
239    /// A flex fraction specified in `fr` units.
240    #[css(dimension)]
241    Fr(CSSFloat),
242    /// `auto`
243    Auto,
244    /// `min-content`
245    MinContent,
246    /// `max-content`
247    MaxContent,
248}
249
250pub use self::GenericTrackBreadth as TrackBreadth;
251
252impl<L> TrackBreadth<L> {
253    /// Check whether this is a `<fixed-breadth>` (i.e., it only has `<length-percentage>`)
254    ///
255    /// <https://drafts.csswg.org/css-grid/#typedef-fixed-breadth>
256    #[inline]
257    pub fn is_fixed(&self) -> bool {
258        matches!(*self, TrackBreadth::Breadth(..))
259    }
260}
261
262/// A `<track-size>` type for explicit grid track sizing. Like `<track-breadth>`, this is
263/// generic only to avoid code bloat. It only takes `<length-percentage>`
264///
265/// <https://drafts.csswg.org/css-grid/#typedef-track-size>
266#[derive(
267    Clone,
268    Debug,
269    MallocSizeOf,
270    PartialEq,
271    SpecifiedValueInfo,
272    ToAnimatedValue,
273    ToComputedValue,
274    ToResolvedValue,
275    ToShmem,
276)]
277#[repr(C, u8)]
278pub enum GenericTrackSize<L> {
279    /// A flexible `<track-breadth>`
280    Breadth(GenericTrackBreadth<L>),
281    /// A `minmax` function for a range over an inflexible `<track-breadth>`
282    /// and a flexible `<track-breadth>`
283    ///
284    /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-minmax>
285    #[css(function)]
286    Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>),
287    /// A `fit-content` function.
288    ///
289    /// This stores a TrackBreadth<L> for convenience, but it can only be a
290    /// LengthPercentage.
291    ///
292    /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content>
293    #[css(function)]
294    FitContent(GenericTrackBreadth<L>),
295}
296
297pub use self::GenericTrackSize as TrackSize;
298
299impl<L> TrackSize<L> {
300    /// The initial value.
301    const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto);
302
303    /// Returns the initial value.
304    pub const fn initial_value() -> Self {
305        Self::INITIAL_VALUE
306    }
307
308    /// Returns true if `self` is the initial value.
309    pub fn is_initial(&self) -> bool {
310        matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585
311    }
312
313    /// Check whether this is a `<fixed-size>`
314    ///
315    /// <https://drafts.csswg.org/css-grid/#typedef-fixed-size>
316    pub fn is_fixed(&self) -> bool {
317        match *self {
318            TrackSize::Breadth(ref breadth) => breadth.is_fixed(),
319            // For minmax function, it could be either
320            // minmax(<fixed-breadth>, <track-breadth>) or minmax(<inflexible-breadth>, <fixed-breadth>),
321            // and since both variants are a subset of minmax(<inflexible-breadth>, <track-breadth>), we only
322            // need to make sure that they're fixed. So, we don't have to modify the parsing function.
323            TrackSize::Minmax(ref breadth_1, ref breadth_2) => {
324                if breadth_1.is_fixed() {
325                    return true; // the second value is always a <track-breadth>
326                }
327
328                match *breadth_1 {
329                    TrackBreadth::Fr(_) => false, // should be <inflexible-breadth> at this point
330                    _ => breadth_2.is_fixed(),
331                }
332            },
333            TrackSize::FitContent(_) => false,
334        }
335    }
336}
337
338impl<L> Default for TrackSize<L> {
339    fn default() -> Self {
340        Self::initial_value()
341    }
342}
343
344impl<L: ToCss> ToCss for TrackSize<L> {
345    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
346    where
347        W: Write,
348    {
349        match *self {
350            TrackSize::Breadth(ref breadth) => breadth.to_css(dest),
351            TrackSize::Minmax(ref min, ref max) => {
352                // According to gecko minmax(auto, <flex>) is equivalent to <flex>,
353                // and both are serialized as <flex>.
354                if let TrackBreadth::Auto = *min {
355                    if let TrackBreadth::Fr(_) = *max {
356                        return max.to_css(dest);
357                    }
358                }
359
360                dest.write_str("minmax(")?;
361                min.to_css(dest)?;
362                dest.write_str(", ")?;
363                max.to_css(dest)?;
364                dest.write_char(')')
365            },
366            TrackSize::FitContent(ref lp) => {
367                dest.write_str("fit-content(")?;
368                lp.to_css(dest)?;
369                dest.write_char(')')
370            },
371        }
372    }
373}
374
375/// A `<track-size>+`.
376/// We use the empty slice as `auto`, and always parse `auto` as an empty slice.
377/// This means it's impossible to have a slice containing only one auto item.
378#[derive(
379    Clone,
380    Debug,
381    Default,
382    MallocSizeOf,
383    PartialEq,
384    SpecifiedValueInfo,
385    ToComputedValue,
386    ToCss,
387    ToResolvedValue,
388    ToShmem,
389    ToTyped,
390)]
391#[repr(transparent)]
392pub struct GenericImplicitGridTracks<T>(
393    #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>,
394);
395
396pub use self::GenericImplicitGridTracks as ImplicitGridTracks;
397
398impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> {
399    /// Returns true if current value is same as its initial value (i.e. auto).
400    pub fn is_initial(&self) -> bool {
401        debug_assert_ne!(
402            *self,
403            ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()]))
404        );
405        self.0.is_empty()
406    }
407}
408
409/// Helper function for serializing identifiers with a prefix and suffix, used
410/// for serializing <line-names> (in grid).
411pub fn concat_serialize_idents<W>(
412    prefix: &str,
413    suffix: &str,
414    slice: &[CustomIdent],
415    sep: &str,
416    dest: &mut CssWriter<W>,
417) -> fmt::Result
418where
419    W: Write,
420{
421    if let Some((ref first, rest)) = slice.split_first() {
422        dest.write_str(prefix)?;
423        first.to_css(dest)?;
424        for thing in rest {
425            dest.write_str(sep)?;
426            thing.to_css(dest)?;
427        }
428
429        dest.write_str(suffix)?;
430    }
431
432    Ok(())
433}
434
435/// The initial argument of the `repeat` function.
436///
437/// <https://drafts.csswg.org/css-grid/#typedef-track-repeat>
438#[derive(
439    Clone,
440    Copy,
441    Debug,
442    MallocSizeOf,
443    PartialEq,
444    SpecifiedValueInfo,
445    ToAnimatedValue,
446    ToComputedValue,
447    ToCss,
448    ToResolvedValue,
449    ToShmem,
450)]
451#[repr(C, u8)]
452pub enum RepeatCount<Integer> {
453    /// A positive integer. This is allowed only for `<track-repeat>` and `<fixed-repeat>`
454    Number(Integer),
455    /// An `<auto-fill>` keyword allowed only for `<auto-repeat>`
456    AutoFill,
457    /// An `<auto-fit>` keyword allowed only for `<auto-repeat>`
458    AutoFit,
459}
460
461impl Parse for RepeatCount<specified::Integer> {
462    fn parse<'i, 't>(
463        context: &ParserContext,
464        input: &mut Parser<'i, 't>,
465    ) -> Result<Self, ParseError<'i>> {
466        if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) {
467            if i.value() > MAX_GRID_LINE {
468                i = specified::Integer::new(MAX_GRID_LINE);
469            }
470            return Ok(RepeatCount::Number(i));
471        }
472        try_match_ident_ignore_ascii_case! { input,
473            "auto-fill" => Ok(RepeatCount::AutoFill),
474            "auto-fit" => Ok(RepeatCount::AutoFit),
475        }
476    }
477}
478
479/// The structure containing `<line-names>` and `<track-size>` values.
480#[derive(
481    Clone,
482    Debug,
483    MallocSizeOf,
484    PartialEq,
485    SpecifiedValueInfo,
486    ToAnimatedValue,
487    ToComputedValue,
488    ToResolvedValue,
489    ToShmem,
490)]
491#[css(function = "repeat")]
492#[repr(C)]
493pub struct GenericTrackRepeat<L, I> {
494    /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`)
495    pub count: RepeatCount<I>,
496    /// `<line-names>` accompanying `<track_size>` values.
497    ///
498    /// If there's no `<line-names>`, then it's represented by an empty vector.
499    /// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's
500    /// length is always one value more than that of the `<track-size>`.
501    pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
502    /// `<track-size>` values.
503    pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>,
504}
505
506pub use self::GenericTrackRepeat as TrackRepeat;
507
508impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> {
509    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
510    where
511        W: Write,
512    {
513        dest.write_str("repeat(")?;
514        self.count.to_css(dest)?;
515        dest.write_str(", ")?;
516
517        let mut line_names_iter = self.line_names.iter();
518        for (i, (ref size, ref names)) in self
519            .track_sizes
520            .iter()
521            .zip(&mut line_names_iter)
522            .enumerate()
523        {
524            if i > 0 {
525                dest.write_char(' ')?;
526            }
527
528            concat_serialize_idents("[", "] ", names, " ", dest)?;
529            size.to_css(dest)?;
530        }
531
532        if let Some(line_names_last) = line_names_iter.next() {
533            concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
534        }
535
536        dest.write_char(')')?;
537
538        Ok(())
539    }
540}
541
542/// Track list values. Can be <track-size> or <track-repeat>
543#[derive(
544    Animate,
545    Clone,
546    Debug,
547    MallocSizeOf,
548    PartialEq,
549    SpecifiedValueInfo,
550    ToAnimatedValue,
551    ToComputedValue,
552    ToCss,
553    ToResolvedValue,
554    ToShmem,
555)]
556#[repr(C, u8)]
557pub enum GenericTrackListValue<LengthPercentage, Integer> {
558    /// A <track-size> value.
559    TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>),
560    /// A <track-repeat> value.
561    TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>),
562}
563
564pub use self::GenericTrackListValue as TrackListValue;
565
566impl<L, I> TrackListValue<L, I> {
567    // FIXME: can't use TrackSize::initial_value() here b/c rustc error "is not yet stable as a const fn"
568    const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto));
569
570    fn is_repeat(&self) -> bool {
571        matches!(*self, TrackListValue::TrackRepeat(..))
572    }
573
574    /// Returns true if `self` is the initial value.
575    pub fn is_initial(&self) -> bool {
576        matches!(
577            *self,
578            TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto))
579        ) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585
580    }
581}
582
583impl<L, I> Default for TrackListValue<L, I> {
584    #[inline]
585    fn default() -> Self {
586        Self::INITIAL_VALUE
587    }
588}
589
590/// A grid `<track-list>` type.
591///
592/// <https://drafts.csswg.org/css-grid/#typedef-track-list>
593#[derive(
594    Clone,
595    Debug,
596    MallocSizeOf,
597    PartialEq,
598    SpecifiedValueInfo,
599    ToAnimatedValue,
600    ToComputedValue,
601    ToResolvedValue,
602    ToShmem,
603)]
604#[repr(C)]
605pub struct GenericTrackList<LengthPercentage, Integer> {
606    /// The index in `values` where our `<auto-repeat>` value is, if in bounds.
607    #[css(skip)]
608    pub auto_repeat_index: usize,
609    /// A vector of `<track-size> | <track-repeat>` values.
610    pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>,
611    /// `<line-names>` accompanying `<track-size> | <track-repeat>` values.
612    ///
613    /// If there's no `<line-names>`, then it's represented by an empty vector.
614    /// For N values, there will be N+1 `<line-names>`, and so this vector's
615    /// length is always one value more than that of the `<track-size>`.
616    pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
617}
618
619pub use self::GenericTrackList as TrackList;
620
621impl<L, I> TrackList<L, I> {
622    /// Whether this track list is an explicit track list (that is, doesn't have
623    /// any repeat values).
624    pub fn is_explicit(&self) -> bool {
625        !self.values.iter().any(|v| v.is_repeat())
626    }
627
628    /// Whether this track list has an `<auto-repeat>` value.
629    pub fn has_auto_repeat(&self) -> bool {
630        self.auto_repeat_index < self.values.len()
631    }
632}
633
634impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> {
635    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
636    where
637        W: Write,
638    {
639        let mut values_iter = self.values.iter().peekable();
640        let mut line_names_iter = self.line_names.iter().peekable();
641
642        for idx in 0.. {
643            let names = line_names_iter.next().unwrap(); // This should exist!
644            concat_serialize_idents("[", "]", names, " ", dest)?;
645
646            match values_iter.next() {
647                Some(value) => {
648                    if !names.is_empty() {
649                        dest.write_char(' ')?;
650                    }
651
652                    value.to_css(dest)?;
653                },
654                None => break,
655            }
656
657            if values_iter.peek().is_some()
658                || line_names_iter.peek().map_or(false, |v| !v.is_empty())
659                || (idx + 1 == self.auto_repeat_index)
660            {
661                dest.write_char(' ')?;
662            }
663        }
664
665        Ok(())
666    }
667}
668
669/// The `<name-repeat>` for subgrids.
670///
671/// <name-repeat> = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+)
672///
673/// https://drafts.csswg.org/css-grid/#typedef-name-repeat
674#[derive(
675    Clone,
676    Debug,
677    MallocSizeOf,
678    PartialEq,
679    SpecifiedValueInfo,
680    ToAnimatedValue,
681    ToComputedValue,
682    ToResolvedValue,
683    ToShmem,
684)]
685#[repr(C)]
686pub struct GenericNameRepeat<I> {
687    /// The number of times for the value to be repeated (could also be `auto-fill`).
688    /// Note: `RepeatCount` accepts `auto-fit`, so we should reject it after parsing it.
689    pub count: RepeatCount<I>,
690    /// This represents `<line-names>+`. The length of the outer vector is at least one.
691    pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>,
692}
693
694pub use self::GenericNameRepeat as NameRepeat;
695
696impl<I: ToCss> ToCss for NameRepeat<I> {
697    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
698    where
699        W: Write,
700    {
701        dest.write_str("repeat(")?;
702        self.count.to_css(dest)?;
703        dest.write_char(',')?;
704
705        for ref names in self.line_names.iter() {
706            if names.is_empty() {
707                // Note: concat_serialize_idents() skip the empty list so we have to handle it
708                // manually for NameRepeat.
709                dest.write_str(" []")?;
710            } else {
711                concat_serialize_idents(" [", "]", names, " ", dest)?;
712            }
713        }
714
715        dest.write_char(')')
716    }
717}
718
719impl<I> NameRepeat<I> {
720    /// Returns true if it is auto-fill.
721    #[inline]
722    pub fn is_auto_fill(&self) -> bool {
723        matches!(self.count, RepeatCount::AutoFill)
724    }
725}
726
727/// A single value for `<line-names>` or `<name-repeat>`.
728#[derive(
729    Clone,
730    Debug,
731    MallocSizeOf,
732    PartialEq,
733    SpecifiedValueInfo,
734    ToAnimatedValue,
735    ToComputedValue,
736    ToResolvedValue,
737    ToShmem,
738)]
739#[repr(C, u8)]
740pub enum GenericLineNameListValue<I> {
741    /// `<line-names>`.
742    LineNames(crate::OwnedSlice<CustomIdent>),
743    /// `<name-repeat>`.
744    Repeat(GenericNameRepeat<I>),
745}
746
747pub use self::GenericLineNameListValue as LineNameListValue;
748
749impl<I: ToCss> ToCss for LineNameListValue<I> {
750    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
751    where
752        W: Write,
753    {
754        match *self {
755            Self::Repeat(ref r) => r.to_css(dest),
756            Self::LineNames(ref names) => {
757                dest.write_char('[')?;
758
759                if let Some((ref first, rest)) = names.split_first() {
760                    first.to_css(dest)?;
761                    for name in rest {
762                        dest.write_char(' ')?;
763                        name.to_css(dest)?;
764                    }
765                }
766
767                dest.write_char(']')
768            },
769        }
770    }
771}
772
773/// The `<line-name-list>` for subgrids.
774///
775/// <line-name-list> = [ <line-names> | <name-repeat> ]+
776/// <name-repeat> = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+)
777///
778/// https://drafts.csswg.org/css-grid/#typedef-line-name-list
779#[derive(
780    Clone,
781    Debug,
782    Default,
783    MallocSizeOf,
784    PartialEq,
785    SpecifiedValueInfo,
786    ToAnimatedValue,
787    ToComputedValue,
788    ToResolvedValue,
789    ToShmem,
790)]
791#[repr(C)]
792pub struct GenericLineNameList<I> {
793    /// The pre-computed length of line_names, without the length of repeat(auto-fill, ...).
794    // We precomputed this at parsing time, so we can avoid an extra loop when expanding
795    // repeat(auto-fill).
796    pub expanded_line_names_length: usize,
797    /// The line name list.
798    pub line_names: crate::OwnedSlice<GenericLineNameListValue<I>>,
799}
800
801pub use self::GenericLineNameList as LineNameList;
802
803impl<I: ToCss> ToCss for LineNameList<I> {
804    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
805    where
806        W: Write,
807    {
808        dest.write_str("subgrid")?;
809
810        for value in self.line_names.iter() {
811            dest.write_char(' ')?;
812            value.to_css(dest)?;
813        }
814
815        Ok(())
816    }
817}
818
819/// Variants for `<grid-template-rows> | <grid-template-columns>`
820#[derive(
821    Animate,
822    Clone,
823    Debug,
824    MallocSizeOf,
825    PartialEq,
826    SpecifiedValueInfo,
827    ToAnimatedValue,
828    ToComputedValue,
829    ToCss,
830    ToResolvedValue,
831    ToShmem,
832    ToTyped,
833)]
834#[value_info(other_values = "subgrid")]
835#[repr(C, u8)]
836pub enum GenericGridTemplateComponent<L, I> {
837    /// `none` value.
838    None,
839    /// The grid `<track-list>`
840    TrackList(
841        #[animation(field_bound)]
842        #[compute(field_bound)]
843        #[resolve(field_bound)]
844        #[shmem(field_bound)]
845        Box<GenericTrackList<L, I>>,
846    ),
847    /// A `subgrid <line-name-list>?`
848    /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec.
849    #[animation(error)]
850    Subgrid(Box<GenericLineNameList<I>>),
851    /// `masonry` value.
852    /// https://github.com/w3c/csswg-drafts/issues/4650
853    Masonry,
854}
855
856pub use self::GenericGridTemplateComponent as GridTemplateComponent;
857
858impl<L, I> GridTemplateComponent<L, I> {
859    /// The initial value.
860    const INITIAL_VALUE: Self = Self::None;
861
862    /// Returns length of the <track-list>s <track-size>
863    pub fn track_list_len(&self) -> usize {
864        match *self {
865            GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(),
866            _ => 0,
867        }
868    }
869
870    /// Returns true if `self` is the initial value.
871    pub fn is_initial(&self) -> bool {
872        matches!(*self, Self::None) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585
873    }
874}
875
876impl<L, I> Default for GridTemplateComponent<L, I> {
877    #[inline]
878    fn default() -> Self {
879        Self::INITIAL_VALUE
880    }
881}