/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Generic types for the handling of //! [grids](https://drafts.csswg.org/css-grid/). use crate::parser::{Parse, ParserContext}; use crate::values::specified; use crate::values::specified::grid::parse_line_names; use crate::values::{CSSFloat, CustomIdent}; use crate::{Atom, Zero}; use cssparser::Parser; use std::fmt::{self, Write}; use std::{cmp, usize}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; /// These are the limits that we choose to clamp grid line numbers to. /// http://drafts.csswg.org/css-grid/#overlarge-grids /// line_num is clamped to this range at parse time. pub const MIN_GRID_LINE: i32 = -10000; /// See above. pub const MAX_GRID_LINE: i32 = 10000; /// A `` type. /// /// #[derive( Clone, Debug, Default, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct GenericGridLine { /// A custom identifier for named lines, or the empty atom otherwise. /// /// pub ident: Atom, /// Denotes the nth grid line from grid item's placement. /// /// This is clamped by MIN_GRID_LINE and MAX_GRID_LINE. /// /// NOTE(emilio): If we ever allow animating these we need to either do /// something more complicated for the clamping, or do this clamping at /// used-value time. pub line_num: Integer, /// Flag to check whether it's a `span` keyword. pub is_span: bool, } pub use self::GenericGridLine as GridLine; impl GridLine where Integer: PartialEq + Zero, { /// The `auto` value. pub fn auto() -> Self { Self { is_span: false, line_num: Zero::zero(), ident: atom!(""), } } /// Check whether this `` represents an `auto` value. pub fn is_auto(&self) -> bool { self.ident == atom!("") && self.line_num.is_zero() && !self.is_span } /// Check whether this `` represents a `` value. pub fn is_ident_only(&self) -> bool { self.ident != atom!("") && self.line_num.is_zero() && !self.is_span } /// Check if `self` makes `other` omittable according to the rules at: /// https://drafts.csswg.org/css-grid/#propdef-grid-column /// https://drafts.csswg.org/css-grid/#propdef-grid-area pub fn can_omit(&self, other: &Self) -> bool { if self.is_ident_only() { self == other } else { other.is_auto() } } } impl ToCss for GridLine where Integer: ToCss + PartialEq + Zero, { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if self.is_auto() { return dest.write_str("auto"); } if self.is_span { dest.write_str("span")?; } if !self.line_num.is_zero() { if self.is_span { dest.write_str(" ")?; } self.line_num.to_css(dest)?; } if self.ident != atom!("") { if self.is_span || !self.line_num.is_zero() { dest.write_str(" ")?; } CustomIdent(self.ident.clone()).to_css(dest)?; } Ok(()) } } impl Parse for GridLine { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let mut grid_line = Self::auto(); if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { return Ok(grid_line); } // | [ && ? ] | [ span && [ || ] ] // This horror is simply, // [ span? && [ || ] ] // And, for some magical reason, "span" should be the first or last value and not in-between. let mut val_before_span = false; for _ in 0..3 { // Maximum possible entities for let location = input.current_source_location(); if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() { if grid_line.is_span { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } if !grid_line.line_num.is_zero() || grid_line.ident != atom!("") { val_before_span = true; } grid_line.is_span = true; } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) { // FIXME(emilio): Probably shouldn't reject if it's calc()... let value = i.value(); if value == 0 || val_before_span || !grid_line.line_num.is_zero() { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } grid_line.line_num = specified::Integer::new(cmp::max( MIN_GRID_LINE, cmp::min(value, MAX_GRID_LINE), )); } else if let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) { if val_before_span || grid_line.ident != atom!("") { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } // NOTE(emilio): `span` is consumed above, so we only need to // reject `auto`. grid_line.ident = CustomIdent::from_ident(location, &name, &["auto"])?.0; } else { break; } } if grid_line.is_auto() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } if grid_line.is_span { if !grid_line.line_num.is_zero() { if grid_line.line_num.value() <= 0 { // disallow negative integers for grid spans return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } } else if grid_line.ident == atom!("") { // integer could be omitted return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } } Ok(grid_line) } } /// A track breadth for explicit grid track sizing. It's generic solely to /// avoid re-implementing it for the computed type. /// /// #[derive( Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericTrackBreadth { /// The generic type is almost always a non-negative `` Breadth(L), /// A flex fraction specified in `fr` units. #[css(dimension)] Fr(CSSFloat), /// `auto` Auto, /// `min-content` MinContent, /// `max-content` MaxContent, } pub use self::GenericTrackBreadth as TrackBreadth; impl TrackBreadth { /// Check whether this is a `` (i.e., it only has ``) /// /// #[inline] pub fn is_fixed(&self) -> bool { matches!(*self, TrackBreadth::Breadth(..)) } } /// A `` type for explicit grid track sizing. Like ``, this is /// generic only to avoid code bloat. It only takes `` /// /// #[derive( Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericTrackSize { /// A flexible `` Breadth(GenericTrackBreadth), /// A `minmax` function for a range over an inflexible `` /// and a flexible `` /// /// #[css(function)] Minmax(GenericTrackBreadth, GenericTrackBreadth), /// A `fit-content` function. /// /// This stores a TrackBreadth for convenience, but it can only be a /// LengthPercentage. /// /// #[css(function)] FitContent(GenericTrackBreadth), } pub use self::GenericTrackSize as TrackSize; impl TrackSize { /// The initial value. const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto); /// Returns the initial value. pub const fn initial_value() -> Self { Self::INITIAL_VALUE } /// Returns true if `self` is the initial value. pub fn is_initial(&self) -> bool { matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 } /// Check whether this is a `` /// /// pub fn is_fixed(&self) -> bool { match *self { TrackSize::Breadth(ref breadth) => breadth.is_fixed(), // For minmax function, it could be either // minmax(, ) or minmax(, ), // and since both variants are a subset of minmax(, ), we only // need to make sure that they're fixed. So, we don't have to modify the parsing function. TrackSize::Minmax(ref breadth_1, ref breadth_2) => { if breadth_1.is_fixed() { return true; // the second value is always a } match *breadth_1 { TrackBreadth::Fr(_) => false, // should be at this point _ => breadth_2.is_fixed(), } }, TrackSize::FitContent(_) => false, } } } impl Default for TrackSize { fn default() -> Self { Self::initial_value() } } impl ToCss for TrackSize { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { TrackSize::Breadth(ref breadth) => breadth.to_css(dest), TrackSize::Minmax(ref min, ref max) => { // According to gecko minmax(auto, ) is equivalent to , // and both are serialized as . if let TrackBreadth::Auto = *min { if let TrackBreadth::Fr(_) = *max { return max.to_css(dest); } } dest.write_str("minmax(")?; min.to_css(dest)?; dest.write_str(", ")?; max.to_css(dest)?; dest.write_str(")") }, TrackSize::FitContent(ref lp) => { dest.write_str("fit-content(")?; lp.to_css(dest)?; dest.write_str(")") }, } } } /// A `+`. /// We use the empty slice as `auto`, and always parse `auto` as an empty slice. /// This means it's impossible to have a slice containing only one auto item. #[derive( Clone, Debug, Default, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(transparent)] pub struct GenericImplicitGridTracks( #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice, ); pub use self::GenericImplicitGridTracks as ImplicitGridTracks; impl ImplicitGridTracks { /// Returns true if current value is same as its initial value (i.e. auto). pub fn is_initial(&self) -> bool { debug_assert_ne!( *self, ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()])) ); self.0.is_empty() } } /// Helper function for serializing identifiers with a prefix and suffix, used /// for serializing (in grid). pub fn concat_serialize_idents( prefix: &str, suffix: &str, slice: &[CustomIdent], sep: &str, dest: &mut CssWriter, ) -> fmt::Result where W: Write, { if let Some((ref first, rest)) = slice.split_first() { dest.write_str(prefix)?; first.to_css(dest)?; for thing in rest { dest.write_str(sep)?; thing.to_css(dest)?; } dest.write_str(suffix)?; } Ok(()) } /// The initial argument of the `repeat` function. /// /// #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum RepeatCount { /// A positive integer. This is allowed only for `` and `` Number(Integer), /// An `` keyword allowed only for `` AutoFill, /// An `` keyword allowed only for `` AutoFit, } impl Parse for RepeatCount { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) { if i.value() > MAX_GRID_LINE { i = specified::Integer::new(MAX_GRID_LINE); } return Ok(RepeatCount::Number(i)); } try_match_ident_ignore_ascii_case! { input, "auto-fill" => Ok(RepeatCount::AutoFill), "auto-fit" => Ok(RepeatCount::AutoFit), } } } /// The structure containing `` and `` values. /// /// It can also hold `repeat()` function parameters, which expands into the respective /// values in its computed form. #[derive( Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[css(function = "repeat")] #[repr(C)] pub struct GenericTrackRepeat { /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`) pub count: RepeatCount, /// `` accompanying `` values. /// /// If there's no ``, then it's represented by an empty vector. /// For N `` values, there will be N+1 ``, and so this vector's /// length is always one value more than that of the ``. pub line_names: crate::OwnedSlice>, /// `` values. pub track_sizes: crate::OwnedSlice>, } pub use self::GenericTrackRepeat as TrackRepeat; impl ToCss for TrackRepeat { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str("repeat(")?; self.count.to_css(dest)?; dest.write_str(", ")?; let mut line_names_iter = self.line_names.iter(); for (i, (ref size, ref names)) in self .track_sizes .iter() .zip(&mut line_names_iter) .enumerate() { if i > 0 { dest.write_str(" ")?; } concat_serialize_idents("[", "] ", names, " ", dest)?; size.to_css(dest)?; } if let Some(line_names_last) = line_names_iter.next() { concat_serialize_idents(" [", "]", line_names_last, " ", dest)?; } dest.write_str(")")?; Ok(()) } } /// Track list values. Can be or #[derive( Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericTrackListValue { /// A value. TrackSize(#[animation(field_bound)] GenericTrackSize), /// A value. TrackRepeat(#[animation(field_bound)] GenericTrackRepeat), } pub use self::GenericTrackListValue as TrackListValue; impl TrackListValue { // FIXME: can't use TrackSize::initial_value() here b/c rustc error "is not yet stable as a const fn" const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)); fn is_repeat(&self) -> bool { matches!(*self, TrackListValue::TrackRepeat(..)) } /// Returns true if `self` is the initial value. pub fn is_initial(&self) -> bool { matches!( *self, TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)) ) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 } } impl Default for TrackListValue { #[inline] fn default() -> Self { Self::INITIAL_VALUE } } /// A grid `` type. /// /// #[derive( Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct GenericTrackList { /// The index in `values` where our `` value is, if in bounds. #[css(skip)] pub auto_repeat_index: usize, /// A vector of ` | ` values. pub values: crate::OwnedSlice>, /// `` accompanying ` | ` values. /// /// If there's no ``, then it's represented by an empty vector. /// For N values, there will be N+1 ``, and so this vector's /// length is always one value more than that of the ``. pub line_names: crate::OwnedSlice>, } pub use self::GenericTrackList as TrackList; impl TrackList { /// Whether this track list is an explicit track list (that is, doesn't have /// any repeat values). pub fn is_explicit(&self) -> bool { !self.values.iter().any(|v| v.is_repeat()) } /// Whether this track list has an `` value. pub fn has_auto_repeat(&self) -> bool { self.auto_repeat_index < self.values.len() } } impl ToCss for TrackList { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { let mut values_iter = self.values.iter().peekable(); let mut line_names_iter = self.line_names.iter().peekable(); for idx in 0.. { let names = line_names_iter.next().unwrap(); // This should exist! concat_serialize_idents("[", "]", names, " ", dest)?; match values_iter.next() { Some(value) => { if !names.is_empty() { dest.write_str(" ")?; } value.to_css(dest)?; }, None => break, } if values_iter.peek().is_some() || line_names_iter.peek().map_or(false, |v| !v.is_empty()) || (idx + 1 == self.auto_repeat_index) { dest.write_str(" ")?; } } Ok(()) } } /// The `` for subgrids. /// /// `subgrid [ | repeat( | auto-fill, +) ]+` /// /// https://drafts.csswg.org/css-grid-2/#typedef-line-name-list #[derive( Clone, Debug, Default, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct LineNameList { /// The optional `` pub names: crate::OwnedSlice>, /// Indicates the starting line names that requires `auto-fill`, if in bounds. pub fill_start: usize, /// Indicates the number of line names in the auto-fill pub fill_len: usize, } impl Parse for LineNameList { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { input.expect_ident_matching("subgrid")?; let mut line_names = vec![]; let mut fill_data = None; // Rather than truncating the result after inserting values, just // have a maximum number of values. This gives us an early out on very // large name lists, but more importantly prevents OOM on huge repeat // expansions. (bug 1583429) let mut max_remaining = MAX_GRID_LINE as usize; loop { let repeat_parse_result = input.try_parse(|input| { input.expect_function_matching("repeat")?; input.parse_nested_block(|input| { let count = RepeatCount::parse(context, input)?; input.expect_comma()?; let mut names_list = vec![]; names_list.push(parse_line_names(input)?); // there should be at least one while let Ok(names) = input.try_parse(parse_line_names) { names_list.push(names); } Ok((names_list, count)) }) }); if let Ok((names_list, count)) = repeat_parse_result { let mut handle_size = |n| { let n = cmp::min(n, max_remaining); max_remaining -= n; n }; match count { // FIXME(emilio): we shouldn't expand repeat() at // parse time for subgrid. (bug 1583429) RepeatCount::Number(num) => { let n = handle_size( num.value() as usize * names_list.len()); line_names.extend( names_list.iter().cloned().cycle().take(n)); }, RepeatCount::AutoFill if fill_data.is_none() => { let fill_idx = line_names.len(); let fill_len = names_list.len(); fill_data = Some((fill_idx, fill_len)); let n = handle_size(fill_len); line_names.extend(names_list.into_iter().take(n)); }, _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } else if let Ok(names) = input.try_parse(parse_line_names) { if max_remaining > 0 { line_names.push(names); max_remaining -= 1; } } else { break; } } debug_assert!(line_names.len() <= MAX_GRID_LINE as usize); let (fill_start, fill_len) = fill_data.unwrap_or((0, 0)); Ok(LineNameList { names: line_names.into(), fill_start: fill_start, fill_len: fill_len, }) } } impl ToCss for LineNameList { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str("subgrid")?; let fill_start = self.fill_start; let fill_len = self.fill_len; for (i, names) in self.names.iter().enumerate() { if fill_len > 0 && i == fill_start { dest.write_str(" repeat(auto-fill,")?; } dest.write_str(" [")?; if let Some((ref first, rest)) = names.split_first() { first.to_css(dest)?; for name in rest { dest.write_str(" ")?; name.to_css(dest)?; } } dest.write_str("]")?; if fill_len > 0 && i == fill_start + fill_len - 1 { dest.write_str(")")?; } } Ok(()) } } /// Variants for ` | ` #[derive( Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericGridTemplateComponent { /// `none` value. None, /// The grid `` TrackList( #[animation(field_bound)] #[compute(field_bound)] #[resolve(field_bound)] #[shmem(field_bound)] Box>, ), /// A `subgrid ?` /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. #[animation(error)] Subgrid(Box), /// `masonry` value. /// https://github.com/w3c/csswg-drafts/issues/4650 Masonry, } pub use self::GenericGridTemplateComponent as GridTemplateComponent; impl GridTemplateComponent { /// The initial value. const INITIAL_VALUE: Self = Self::None; /// Returns length of the s pub fn track_list_len(&self) -> usize { match *self { GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(), _ => 0, } } /// Returns true if `self` is the initial value. pub fn is_initial(&self) -> bool { matches!(*self, Self::None) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 } } impl Default for GridTemplateComponent { #[inline] fn default() -> Self { Self::INITIAL_VALUE } }