Skip to main content

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