Skip to content

Commit

Permalink
Add parsing/serialization for repeat() function
Browse files Browse the repository at this point in the history
  • Loading branch information
wafflespeanut committed May 18, 2017
1 parent d3e394c commit 81b4e64
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 2 deletions.
144 changes: 142 additions & 2 deletions components/style/values/specified/grid.rs
Expand Up @@ -4,7 +4,7 @@

//! Necessary types for [grid](https://drafts.csswg.org/css-grid/).

use cssparser::{Parser, Token};
use cssparser::{Parser, Token, serialize_identifier};
use parser::{Parse, ParserContext};
use std::ascii::AsciiExt;
use std::fmt;
Expand Down Expand Up @@ -294,7 +294,7 @@ impl<L: ToCss> ToCss for TrackSize<L> {
TrackSize::MinMax(ref infexible, ref flexible) => {
try!(dest.write_str("minmax("));
try!(infexible.to_css(dest));
try!(dest.write_str(","));
try!(dest.write_str(", "));
try!(flexible.to_css(dest));
dest.write_str(")")
},
Expand Down Expand Up @@ -364,6 +364,24 @@ pub fn parse_line_names(input: &mut Parser) -> Result<Vec<String>, ()> {
})
}

fn concat_serialize_idents<W>(prefix: &str, suffix: &str,
slice: &[String], sep: &str, dest: &mut W) -> fmt::Result
where W: fmt::Write
{
if let Some((ref first, rest)) = slice.split_first() {
dest.write_str(prefix)?;
serialize_identifier(first, dest)?;
for thing in rest {
dest.write_str(sep)?;
serialize_identifier(thing, dest)?;
}

dest.write_str(suffix)?;
}

Ok(())
}

/// The initial argument of the `repeat` function.
///
/// https://drafts.csswg.org/css-grid/#typedef-track-repeat
Expand Down Expand Up @@ -408,3 +426,125 @@ impl ToCss for RepeatCount {

impl ComputedValueAsSpecified for RepeatCount {}
no_viewport_percentage!(RepeatCount);

/// The type of `repeat` function (only used in parsing).
///
/// https://drafts.csswg.org/css-grid/#typedef-track-repeat
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
enum RepeatType {
/// [`<auto-repeat>`](https://drafts.csswg.org/css-grid/#typedef-auto-repeat)
Auto,
/// [`<track-repeat>`](https://drafts.csswg.org/css-grid/#typedef-track-repeat)
Normal,
/// [`<fixed-repeat>`](https://drafts.csswg.org/css-grid/#typedef-fixed-repeat)
Fixed,
}

/// The structure containing `<line-names>` and `<track-size>` values.
///
/// It can also hold `repeat()` function parameters, which expands into the respective
/// values in its computed form.
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct TrackRepeat<L> {
/// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`)
pub count: RepeatCount,
/// `<line-names>` accompanying `<track_size>` values.
///
/// If there's no `<line-names>`, then it's represented by an empty vector.
/// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's
/// length is always one value more than that of the `<track-size>`.
pub line_names: Vec<Vec<String>>,
/// `<track-size>` values.
pub track_sizes: Vec<TrackSize<L>>,
}

impl TrackRepeat<LengthOrPercentage> {
fn parse_with_repeat_type(context: &ParserContext, input: &mut Parser)
-> Result<(TrackRepeat<LengthOrPercentage>, RepeatType), ()> {
input.try(|i| i.expect_function_matching("repeat")).and_then(|_| {
input.parse_nested_block(|input| {
let count = RepeatCount::parse(context, input)?;
input.expect_comma()?;

let is_auto = count == RepeatCount::AutoFit || count == RepeatCount::AutoFill;
let mut repeat_type = if is_auto {
RepeatType::Auto
} else { // <fixed-size> is a subset of <track_size>, so it should work for both
RepeatType::Fixed
};

let mut names = vec![];
let mut values = vec![];
let mut current_names;

loop {
current_names = input.try(parse_line_names).unwrap_or(vec![]);
if let Ok(track_size) = input.try(|i| TrackSize::parse(context, i)) {
if !track_size.is_fixed() {
if is_auto {
return Err(()) // should be <fixed-size> for <auto-repeat>
}

if repeat_type == RepeatType::Fixed {
repeat_type = RepeatType::Normal // <track-size> for sure
}
}

values.push(track_size);
names.push(current_names);
} else {
if values.is_empty() {
return Err(()) // expecting at least one <track-size>
}

names.push(current_names); // final `<line-names>`
break // no more <track-size>, breaking
}
}

let repeat = TrackRepeat {
count: count,
track_sizes: values,
line_names: names,
};

Ok((repeat, repeat_type))
})
})
}
}

impl<L: ToCss> ToCss for TrackRepeat<L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::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(())
}
}

impl HasViewportPercentage for TrackRepeat<LengthOrPercentage> {
#[inline]
fn has_viewport_percentage(&self) -> bool {
self.track_sizes.iter().any(|ref v| v.has_viewport_percentage())
}
}
1 change: 1 addition & 0 deletions components/style_traits/values.rs
Expand Up @@ -74,6 +74,7 @@ macro_rules! impl_to_css_for_predefined_type {

impl_to_css_for_predefined_type!(f32);
impl_to_css_for_predefined_type!(i32);
impl_to_css_for_predefined_type!(u16);
impl_to_css_for_predefined_type!(u32);
impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
impl_to_css_for_predefined_type!(::cssparser::RGBA);
Expand Down

0 comments on commit 81b4e64

Please sign in to comment.