style: Implement parsing for CSS conic-gradient syntax.

Differential Revision: https://phabricator.services.mozilla.com/D62148
This commit is contained in:
Tim Nguyen 2020-02-19 17:43:04 +00:00 committed by Emilio Cobos Álvarez
parent 31187b0180
commit 61712d1a03
3 changed files with 114 additions and 31 deletions

View file

@ -13,7 +13,7 @@ use crate::values::computed::url::ComputedImageUrl;
use crate::values::computed::NumberOrPercentage; use crate::values::computed::NumberOrPercentage;
use crate::values::computed::{Angle, Color, Context}; use crate::values::computed::{Angle, Color, Context};
use crate::values::computed::{ use crate::values::computed::{
LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, ToComputedValue, AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, ToComputedValue,
}; };
use crate::values::generics::image::{self as generic, GradientCompatMode}; use crate::values::generics::image::{self as generic, GradientCompatMode};
use crate::values::specified::image::LineDirection as SpecifiedLineDirection; use crate::values::specified::image::LineDirection as SpecifiedLineDirection;
@ -34,6 +34,8 @@ pub type Gradient = generic::GenericGradient<
NonNegativeLength, NonNegativeLength,
NonNegativeLengthPercentage, NonNegativeLengthPercentage,
Position, Position,
Angle,
AngleOrPercentage,
Color, Color,
>; >;

View file

@ -9,6 +9,7 @@
use crate::custom_properties; use crate::custom_properties;
use crate::values::serialize_atom_identifier; use crate::values::serialize_atom_identifier;
use crate::Atom; use crate::Atom;
use crate::Zero;
use servo_arc::Arc; use servo_arc::Arc;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
@ -57,6 +58,8 @@ pub enum GenericGradient<
NonNegativeLength, NonNegativeLength,
NonNegativeLengthPercentage, NonNegativeLengthPercentage,
Position, Position,
Angle,
AngleOrPercentage,
Color, Color,
> { > {
/// A linear gradient. /// A linear gradient.
@ -83,6 +86,17 @@ pub enum GenericGradient<
/// Compatibility mode. /// Compatibility mode.
compat_mode: GradientCompatMode, compat_mode: GradientCompatMode,
}, },
/// A conic gradient.
Conic {
/// Start angle of gradient
angle: Angle,
/// Center of gradient
position: Position,
/// The color stops and interpolation hints.
items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>,
/// True if this is a repeating gradient.
repeating: bool,
},
} }
pub use self::GenericGradient as Gradient; pub use self::GenericGradient as Gradient;
@ -308,13 +322,15 @@ where
} }
} }
impl<D, LP, NL, NLP, P, C> ToCss for Gradient<D, LP, NL, NLP, P, C> impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C>
where where
D: LineDirection, D: LineDirection,
LP: ToCss, LP: ToCss,
NL: ToCss, NL: ToCss,
NLP: ToCss, NLP: ToCss,
P: ToCss, P: ToCss,
A: ToCss,
AoP: ToCss,
C: ToCss, C: ToCss,
{ {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
@ -324,6 +340,7 @@ where
let (compat_mode, repeating) = match *self { let (compat_mode, repeating) = match *self {
Gradient::Linear { compat_mode, repeating, .. } => (compat_mode, repeating), Gradient::Linear { compat_mode, repeating, .. } => (compat_mode, repeating),
Gradient::Radial { compat_mode, repeating, .. } => (compat_mode, repeating), Gradient::Radial { compat_mode, repeating, .. } => (compat_mode, repeating),
Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating),
}; };
match compat_mode { match compat_mode {
@ -336,17 +353,24 @@ where
dest.write_str("repeating-")?; dest.write_str("repeating-")?;
} }
let (items, mut skip_comma) = match *self { match *self {
Gradient::Linear { ref direction, compat_mode, ref items, .. } => { Gradient::Linear { ref direction, ref items, compat_mode, .. } => {
dest.write_str("linear-gradient(")?; dest.write_str("linear-gradient(")?;
if !direction.points_downwards(compat_mode) { let mut skip_comma = if !direction.points_downwards(compat_mode) {
direction.to_css(dest, compat_mode)?; direction.to_css(dest, compat_mode)?;
(items, false) false
} else { } else {
(items, true) true
};
for item in &**items {
if !skip_comma {
dest.write_str(", ")?;
}
skip_comma = false;
item.to_css(dest)?;
} }
}, },
Gradient::Radial { ref shape, ref position, compat_mode, ref items, .. } => { Gradient::Radial { ref shape, ref position, ref items, compat_mode, .. } => {
dest.write_str("radial-gradient(")?; dest.write_str("radial-gradient(")?;
let omit_shape = match *shape { let omit_shape = match *shape {
EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
@ -367,15 +391,25 @@ where
shape.to_css(dest)?; shape.to_css(dest)?;
} }
} }
(items, false) for item in &**items {
dest.write_str(", ")?;
item.to_css(dest)?;
}
},
Gradient::Conic { ref angle, ref position, ref items, .. } => {
dest.write_str("conic-gradient(")?;
if !angle.is_zero() {
dest.write_str("from ")?;
angle.to_css(dest)?;
dest.write_str(" ")?;
}
dest.write_str("at ")?;
position.to_css(dest)?;
for item in &**items {
dest.write_str(", ")?;
item.to_css(dest)?;
}
}, },
};
for item in &**items {
if !skip_comma {
dest.write_str(", ")?;
}
skip_comma = false;
item.to_css(dest)?;
} }
dest.write_str(")") dest.write_str(")")
} }

View file

@ -19,7 +19,7 @@ use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPosi
use crate::values::specified::position::{Position, PositionComponent, Side}; use crate::values::specified::position::{Position, PositionComponent, Side};
use crate::values::specified::url::SpecifiedImageUrl; use crate::values::specified::url::SpecifiedImageUrl;
use crate::values::specified::{ use crate::values::specified::{
Angle, Color, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, Angle, AngleOrPercentage, Color, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
}; };
use crate::values::specified::{Number, NumberOrPercentage, Percentage}; use crate::values::specified::{Number, NumberOrPercentage, Percentage};
use crate::Atom; use crate::Atom;
@ -44,6 +44,8 @@ pub type Gradient = generic::Gradient<
NonNegativeLength, NonNegativeLength,
NonNegativeLengthPercentage, NonNegativeLengthPercentage,
Position, Position,
Angle,
AngleOrPercentage,
Color, Color,
>; >;
@ -69,6 +71,13 @@ impl SpecifiedValueInfo for Gradient {
"-moz-repeating-radial-gradient", "-moz-repeating-radial-gradient",
"-webkit-gradient", "-webkit-gradient",
]); ]);
if static_prefs::pref!("layout.css.conic-gradient.enabled") {
f(&[
"conic-gradient",
"repeating-conic-gradient",
]);
}
} }
} }
@ -188,6 +197,7 @@ impl Parse for Gradient {
enum Shape { enum Shape {
Linear, Linear,
Radial, Radial,
Conic,
} }
let func = input.expect_function()?; let func = input.expect_function()?;
@ -232,6 +242,12 @@ impl Parse for Gradient {
"-moz-repeating-radial-gradient" => { "-moz-repeating-radial-gradient" => {
(Shape::Radial, true, GradientCompatMode::Moz) (Shape::Radial, true, GradientCompatMode::Moz)
}, },
"conic-gradient" if static_prefs::pref!("layout.css.conic-gradient.enabled") => {
(Shape::Conic, false, GradientCompatMode::Modern)
},
"repeating-conic-gradient" if static_prefs::pref!("layout.css.conic-gradient.enabled") => {
(Shape::Conic, true, GradientCompatMode::Modern)
},
"-webkit-gradient" => { "-webkit-gradient" => {
return input.parse_nested_block(|i| { return input.parse_nested_block(|i| {
Self::parse_webkit_gradient_argument(context, i) Self::parse_webkit_gradient_argument(context, i)
@ -247,6 +263,7 @@ impl Parse for Gradient {
Ok(match shape { Ok(match shape {
Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?, Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?,
Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?, Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?,
Shape::Conic => Self::parse_conic(context, i, repeating)?,
}) })
})?) })?)
} }
@ -505,12 +522,12 @@ impl Gradient {
Ok(items.into()) Ok(items.into())
} }
/// Not used for -webkit-gradient syntax. /// Not used for -webkit-gradient syntax and conic-gradient
fn parse_stops<'i, 't>( fn parse_stops<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<LengthPercentageItemList, ParseError<'i>> { ) -> Result<LengthPercentageItemList, ParseError<'i>> {
let items = generic::GradientItem::parse_comma_separated(context, input)?; let items = generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?;
if items.len() < 2 { if items.len() < 2 {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
} }
@ -595,6 +612,40 @@ impl Gradient {
compat_mode, compat_mode,
}) })
} }
fn parse_conic<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
repeating: bool,
) -> Result<Self, ParseError<'i>> {
let angle = input.try(|i| {
i.expect_ident_matching("from")?;
// Spec allows unitless zero start angles
// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle
Angle::parse_with_unitless(context, i)
});
let position = input.try(|i| {
i.expect_ident_matching("at")?;
Position::parse(context, i)
});
if angle.is_ok() || position.is_ok() {
input.expect_comma()?;
}
let angle = angle.unwrap_or(Angle::zero());
let position = position.unwrap_or(Position::center());
let items = generic::GradientItem::parse_comma_separated(context, input, AngleOrPercentage::parse_with_unitless)?;
if items.len() < 2 {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(Gradient::Conic {
angle,
position,
items,
repeating,
})
}
} }
impl generic::LineDirection for LineDirection { impl generic::LineDirection for LineDirection {
@ -798,13 +849,11 @@ impl ShapeExtent {
} }
} }
impl<T> generic::GradientItem<Color, T> impl<T> generic::GradientItem<Color, T> {
where
T: Parse,
{
fn parse_comma_separated<'i, 't>( fn parse_comma_separated<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>> + Copy,
) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> { ) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> {
let mut items = Vec::new(); let mut items = Vec::new();
let mut seen_stop = false; let mut seen_stop = false;
@ -812,16 +861,16 @@ where
loop { loop {
input.parse_until_before(Delimiter::Comma, |input| { input.parse_until_before(Delimiter::Comma, |input| {
if seen_stop { if seen_stop {
if let Ok(hint) = input.try(|i| T::parse(context, i)) { if let Ok(hint) = input.try(|i| parse_position(context, i)) {
seen_stop = false; seen_stop = false;
items.push(generic::GradientItem::InterpolationHint(hint)); items.push(generic::GradientItem::InterpolationHint(hint));
return Ok(()); return Ok(());
} }
} }
let stop = generic::ColorStop::parse(context, input)?; let stop = generic::ColorStop::parse(context, input, parse_position)?;
if let Ok(multi_position) = input.try(|i| T::parse(context, i)) { if let Ok(multi_position) = input.try(|i| parse_position(context, i)) {
let stop_color = stop.color.clone(); let stop_color = stop.color.clone();
items.push(stop.into_item()); items.push(stop.into_item());
items.push( items.push(
@ -853,17 +902,15 @@ where
} }
} }
impl<T> Parse for generic::ColorStop<Color, T> impl<T> generic::ColorStop<Color, T> {
where
T: Parse,
{
fn parse<'i, 't>( fn parse<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
Ok(generic::ColorStop { Ok(generic::ColorStop {
color: Color::parse(context, input)?, color: Color::parse(context, input)?,
position: input.try(|i| T::parse(context, i)).ok(), position: input.try(|i| parse_position(context, i)).ok(),
}) })
} }
} }