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