Auto merge of #17268 - servo:grid, r=wafflespeanut,canaltinova

Add support for subgrids and fix some other grid properties

This covers the `subgrid <line-name-lists>?` for `grid-template` (for gecko, and hence, stylo).

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17268)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-07-03 17:10:25 -07:00 committed by GitHub
commit 7a71035793
8 changed files with 384 additions and 113 deletions

View file

@ -5,13 +5,14 @@
//! Generic types for the handling of
//! [grids](https://drafts.csswg.org/css-grid/).
use cssparser::{Parser, serialize_identifier};
use cssparser::Parser;
use parser::{Parse, ParserContext};
use std::{fmt, mem, usize};
use style_traits::{ToCss, ParseError, StyleParseError};
use values::{CSSFloat, CustomIdent};
use values::computed::{self, ComputedValueAsSpecified, Context, ToComputedValue};
use values::specified::Integer;
use values::specified::grid::parse_line_names;
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@ -251,6 +252,13 @@ impl<L> Default for TrackSize<L> {
}
}
impl<L: PartialEq> TrackSize<L> {
/// Returns true if current TrackSize is same as default.
pub fn is_default(&self) -> bool {
*self == TrackSize::default()
}
}
impl<L: ToCss> ToCss for TrackSize<L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
@ -307,15 +315,15 @@ impl<L: ToComputedValue> ToComputedValue for TrackSize<L> {
/// Helper function for serializing identifiers with a prefix and suffix, used
/// for serializing <line-names> (in grid).
pub fn concat_serialize_idents<W>(prefix: &str, suffix: &str,
slice: &[String], sep: &str, dest: &mut W) -> fmt::Result
slice: &[CustomIdent], 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)?;
first.to_css(dest)?;
for thing in rest {
dest.write_str(sep)?;
serialize_identifier(thing, dest)?;
thing.to_css(dest)?;
}
dest.write_str(suffix)?;
@ -372,33 +380,52 @@ pub struct TrackRepeat<L> {
/// 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>>,
pub line_names: Vec<Vec<CustomIdent>>,
/// `<track-size>` values.
pub track_sizes: Vec<TrackSize<L>>,
}
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(", ")?;
// If repeat count is an integer instead of a keyword, it should'n serialized
// with `repeat` function. It should serialized with `N` repeated form.
let repeat_count = match self.count {
RepeatCount::Number(integer) => integer.value(),
_ => {
dest.write_str("repeat(")?;
self.count.to_css(dest)?;
dest.write_str(", ")?;
1
},
};
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 {
for i in 0..repeat_count {
if i != 0 {
dest.write_str(" ")?;
}
concat_serialize_idents("[", "] ", names, " ", dest)?;
size.to_css(dest)?;
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)?;
}
}
if let Some(line_names_last) = line_names_iter.next() {
concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
match self.count {
RepeatCount::AutoFill | RepeatCount::AutoFit => {
dest.write_str(")")?;
},
_ => {},
}
dest.write_str(")")?;
Ok(())
}
}
@ -502,7 +529,7 @@ pub struct TrackList<T> {
/// If there's no `<line-names>`, then it's represented by an empty vector.
/// For N 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>>,
pub line_names: Vec<Vec<CustomIdent>>,
/// `<auto-repeat>` value after computation. This field is necessary, because
/// the `values` field (after computation) will only contain `<track-size>` values, and
/// we need something to represent this function.
@ -551,3 +578,115 @@ impl<T: ToCss> ToCss for TrackList<T> {
Ok(())
}
}
/// The `<line-name-list>` for subgrids.
///
/// `subgrid [ <line-names> | repeat(<positive-integer> | auto-fill, <line-names>+) ]+`
/// Old spec: https://www.w3.org/TR/2015/WD-css-grid-1-20150917/#typedef-line-name-list
#[derive(Clone, PartialEq, Debug, Default)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct LineNameList {
/// The optional `<line-name-list>`
pub names: Vec<Vec<CustomIdent>>,
/// Indicates the line name that requires `auto-fill`
pub fill_idx: Option<u32>,
}
impl Parse for LineNameList {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
input.expect_ident_matching("subgrid")?;
let mut line_names = vec![];
let mut fill_idx = None;
loop {
let repeat_parse_result = input.try(|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_line_names) {
names_list.push(names);
}
Ok((names_list, count))
})
});
if let Ok((mut names_list, count)) = repeat_parse_result {
match count {
RepeatCount::Number(num) =>
line_names.extend(names_list.iter().cloned().cycle()
.take(num.value() as usize * names_list.len())),
RepeatCount::AutoFill if fill_idx.is_none() => {
// `repeat(autof-fill, ..)` should have just one line name.
if names_list.len() > 1 {
return Err(StyleParseError::UnspecifiedError.into());
}
let names = names_list.pop().expect("expected one name list for auto-fill");
line_names.push(names);
fill_idx = Some(line_names.len() as u32 - 1);
},
_ => return Err(StyleParseError::UnspecifiedError.into()),
}
} else if let Ok(names) = input.try(parse_line_names) {
line_names.push(names);
} else {
break
}
}
Ok(LineNameList {
names: line_names,
fill_idx: fill_idx,
})
}
}
impl ToCss for LineNameList {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_str("subgrid")?;
let fill_idx = self.fill_idx.map(|v| v as usize).unwrap_or(usize::MAX);
for (i, names) in self.names.iter().enumerate() {
if i == fill_idx {
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 i == fill_idx {
dest.write_str(")")?;
}
}
Ok(())
}
}
impl ComputedValueAsSpecified for LineNameList {}
no_viewport_percentage!(LineNameList);
/// Variants for `<grid-template-rows> | <grid-template-columns>`
/// Subgrid deferred to Level 2 spec due to lack of implementation.
/// But it's implemented in gecko, so we have to as well.
#[derive(Clone, PartialEq, Debug, ToCss)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum GridTemplateComponent<L> {
/// `none` value.
None,
/// The grid `<track-list>`
TrackList(TrackList<L>),
/// A `subgrid <line-name-list>?`
Subgrid(LineNameList),
}