Implement -webkit-gradient() (fixes #16542)

This commit is contained in:
Anthony Ramine 2017-05-13 16:46:58 +02:00
parent 9e6f9db127
commit ea4e7299d4
4 changed files with 285 additions and 16 deletions

View file

@ -717,7 +717,7 @@ ${helpers.predefined_type("border-image-source", "ImageLayer",
let mut values = vec![];
for _ in 0..4 {
let value = input.try(|input| NumberOrPercentage::parse(context, input));
let value = input.try(|input| NumberOrPercentage::parse_non_negative(context, input));
match value {
Ok(val) => values.push(val),
Err(_) => break,

View file

@ -12,6 +12,7 @@ use cssparser::{Parser, Token};
use parser::{Parse, ParserContext};
#[cfg(feature = "servo")]
use servo_url::ServoUrl;
use std::cmp::Ordering;
use std::f32::consts::PI;
use std::fmt;
use style_traits::ToCss;
@ -21,8 +22,10 @@ use values::generics::image::{EndingShape as GenericEndingShape, Gradient as Gen
use values::generics::image::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind};
use values::generics::image::{Image as GenericImage, ImageRect as GenericImageRect};
use values::generics::image::{LineDirection as GenericsLineDirection, ShapeExtent};
use values::specified::{Angle, CSSColor, Length, LengthOrPercentage, NumberOrPercentage, Percentage};
use values::specified::position::{Position, X, Y};
use values::generics::position::Position as GenericPosition;
use values::specified::{Angle, CSSColor, Color, Length, LengthOrPercentage};
use values::specified::{Number, NumberOrPercentage, Percentage};
use values::specified::position::{Position, PositionComponent, Side, X, Y};
use values::specified::url::SpecifiedUrl;
/// A specified image layer.
@ -145,6 +148,9 @@ impl Parse for Gradient {
"-webkit-repeating-radial-gradient" => {
(Shape::Radial, true, CompatMode::WebKit)
},
"-webkit-gradient" => {
return input.parse_nested_block(|i| Self::parse_webkit_gradient_argument(context, i));
},
_ => { return Err(()); }
};
@ -170,6 +176,252 @@ impl Parse for Gradient {
}
}
impl Gradient {
fn parse_webkit_gradient_argument(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
type Point = GenericPosition<Component<X>, Component<Y>>;
#[derive(Clone, Copy)]
enum Component<S> {
Center,
Number(NumberOrPercentage),
Side(S),
}
impl LineDirection {
fn from_points(first: Point, second: Point) -> Self {
let h_ord = first.horizontal.partial_cmp(&second.horizontal);
let v_ord = first.vertical.partial_cmp(&second.vertical);
let (h, v) = match (h_ord, v_ord) {
(Some(h), Some(v)) => (h, v),
_ => return LineDirection::Vertical(Y::Bottom),
};
match (h, v) {
(Ordering::Less, Ordering::Less) => {
LineDirection::Corner(X::Right, Y::Bottom)
},
(Ordering::Less, Ordering::Equal) => {
LineDirection::Horizontal(X::Right)
},
(Ordering::Less, Ordering::Greater) => {
LineDirection::Corner(X::Right, Y::Top)
},
(Ordering::Equal, Ordering::Greater) => {
LineDirection::Vertical(Y::Top)
},
(Ordering::Equal, Ordering::Equal) |
(Ordering::Equal, Ordering::Less) => {
LineDirection::Vertical(Y::Bottom)
},
(Ordering::Greater, Ordering::Less) => {
LineDirection::Corner(X::Left, Y::Bottom)
},
(Ordering::Greater, Ordering::Equal) => {
LineDirection::Horizontal(X::Left)
},
(Ordering::Greater, Ordering::Greater) => {
LineDirection::Corner(X::Left, Y::Top)
},
}
}
}
impl From<Point> for Position {
fn from(point: Point) -> Self {
Self::new(point.horizontal.into(), point.vertical.into())
}
}
impl Parse for Point {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
input.try(|i| {
let x = Component::parse(context, i)?;
let y = Component::parse(context, i)?;
Ok(Self::new(x, y))
})
}
}
impl<S: Side> From<Component<S>> for NumberOrPercentage {
fn from(component: Component<S>) -> Self {
match component {
Component::Center => NumberOrPercentage::Percentage(Percentage(0.5)),
Component::Number(number) => number,
Component::Side(side) => {
let p = Percentage(if side.is_start() { 0. } else { 1. });
NumberOrPercentage::Percentage(p)
},
}
}
}
impl<S: Side> From<Component<S>> for PositionComponent<S> {
fn from(component: Component<S>) -> Self {
match component {
Component::Center => {
PositionComponent::Center
},
Component::Number(NumberOrPercentage::Number(number)) => {
PositionComponent::Length(Length::from_px(number.value).into())
},
Component::Number(NumberOrPercentage::Percentage(p)) => {
PositionComponent::Length(p.into())
},
Component::Side(side) => {
PositionComponent::Side(side, None)
},
}
}
}
impl<S: Copy + Side> Component<S> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (NumberOrPercentage::from(*self), NumberOrPercentage::from(*other)) {
(NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => {
a.0.partial_cmp(&b.0)
},
(NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => {
a.value.partial_cmp(&b.value)
},
(_, _) => {
None
}
}
}
}
impl<S: Parse> Parse for Component<S> {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
if let Ok(side) = input.try(|i| S::parse(context, i)) {
return Ok(Component::Side(side));
}
if let Ok(number) = input.try(|i| NumberOrPercentage::parse(context, i)) {
return Ok(Component::Number(number));
}
input.try(|i| i.expect_ident_matching("center"))?;
Ok(Component::Center)
}
}
let ident = input.expect_ident()?;
input.expect_comma()?;
let (kind, reverse_stops) = match_ignore_ascii_case! { &ident,
"linear" => {
let first = Point::parse(context, input)?;
input.expect_comma()?;
let second = Point::parse(context, input)?;
let direction = LineDirection::from_points(first, second);
let kind = GenericGradientKind::Linear(direction);
(kind, false)
},
"radial" => {
let first_point = Point::parse(context, input)?;
input.expect_comma()?;
let first_radius = Number::parse(context, input)?;
input.expect_comma()?;
let second_point = Point::parse(context, input)?;
input.expect_comma()?;
let second_radius = Number::parse(context, input)?;
let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value {
(false, second_point, second_radius)
} else {
(true, first_point, first_radius)
};
let shape = GenericEndingShape::Circle(Circle::Radius(Length::from_px(radius.value)));
let position = point.into();
let kind = GenericGradientKind::Radial(shape, position);
(kind, reverse_stops)
},
_ => return Err(()),
};
let mut items = input.try(|i| {
i.expect_comma()?;
i.parse_comma_separated(|i| {
let function = i.expect_function()?;
let (color, mut p) = i.parse_nested_block(|i| {
let p = match_ignore_ascii_case! { &function,
"color-stop" => {
let p = match NumberOrPercentage::parse(context, i)? {
NumberOrPercentage::Number(number) => number.value,
NumberOrPercentage::Percentage(p) => p.0,
};
i.expect_comma()?;
p
},
"from" => 0.,
"to" => 1.,
_ => return Err(()),
};
let color = CSSColor::parse(context, i)?;
if color.parsed == Color::CurrentColor {
return Err(());
}
Ok((color, p))
})?;
if reverse_stops {
p = 1. - p;
}
Ok(GenericGradientItem::ColorStop(GenericColorStop {
color: color,
position: Some(LengthOrPercentage::Percentage(Percentage(p))),
}))
})
}).unwrap_or(vec![]);
if items.is_empty() {
items = vec![
GenericGradientItem::ColorStop(GenericColorStop {
color: CSSColor::transparent(),
position: Some(Percentage(0.).into()),
}),
GenericGradientItem::ColorStop(GenericColorStop {
color: CSSColor::transparent(),
position: Some(Percentage(1.).into()),
}),
];
} else if items.len() == 1 {
let first = items[0].clone();
items.push(first);
} else {
items.sort_by(|a, b| {
match (a, b) {
(&GenericGradientItem::ColorStop(ref a), &GenericGradientItem::ColorStop(ref b)) => {
match (&a.position, &b.position) {
(&Some(LengthOrPercentage::Percentage(a)), &Some(LengthOrPercentage::Percentage(b))) => {
let ordering = a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal);
if ordering != Ordering::Equal {
return ordering;
}
},
_ => {},
}
},
_ => {},
}
if reverse_stops {
Ordering::Greater
} else {
Ordering::Less
}
})
}
Ok(GenericGradient {
kind: kind,
items: items,
repeating: false,
compat_mode: CompatMode::Modern,
})
}
}
impl GradientKind {
fn parse_linear(context: &ParserContext,
input: &mut Parser,
@ -408,13 +660,13 @@ impl Parse for ImageRect {
let string = i.expect_url_or_string()?;
let url = SpecifiedUrl::parse_from_string(string, context)?;
i.expect_comma()?;
let top = NumberOrPercentage::parse(context, i)?;
let top = NumberOrPercentage::parse_non_negative(context, i)?;
i.expect_comma()?;
let right = NumberOrPercentage::parse(context, i)?;
let right = NumberOrPercentage::parse_non_negative(context, i)?;
i.expect_comma()?;
let bottom = NumberOrPercentage::parse(context, i)?;
let bottom = NumberOrPercentage::parse_non_negative(context, i)?;
i.expect_comma()?;
let left = NumberOrPercentage::parse(context, i)?;
let left = NumberOrPercentage::parse_non_negative(context, i)?;
Ok(ImageRect {
url: url,

View file

@ -15,7 +15,7 @@ use std::{cmp, fmt, mem};
use std::ascii::AsciiExt;
use std::ops::Mul;
use style_traits::ToCss;
use style_traits::values::specified::AllowedLengthType;
use style_traits::values::specified::{AllowedLengthType, AllowedNumericType};
use stylesheets::CssRuleType;
use super::{AllowQuirks, Number, ToComputedValue};
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
@ -729,7 +729,10 @@ impl ToCss for Percentage {
}
impl Percentage {
fn parse_internal(input: &mut Parser, context: AllowedLengthType) -> Result<Self, ()> {
/// Parse a specific kind of percentage.
pub fn parse_with_clamping_mode(input: &mut Parser,
context: AllowedNumericType)
-> Result<Self, ()> {
match try!(input.next()) {
Token::Percentage(ref value) if context.is_ok(value.unit_value) => {
Ok(Percentage(value.unit_value))
@ -740,14 +743,14 @@ impl Percentage {
/// Parses a percentage token, but rejects it if it's negative.
pub fn parse_non_negative(input: &mut Parser) -> Result<Self, ()> {
Self::parse_internal(input, AllowedLengthType::NonNegative)
Self::parse_with_clamping_mode(input, AllowedNumericType::NonNegative)
}
}
impl Parse for Percentage {
#[inline]
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
Self::parse_internal(input, AllowedLengthType::All)
Self::parse_with_clamping_mode(input, AllowedNumericType::All)
}
}

View file

@ -658,7 +658,7 @@ impl ToCss for Number {
/// <number-percentage>
/// Accepts only non-negative numbers.
#[derive(Debug, Clone, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum NumberOrPercentage {
@ -668,13 +668,27 @@ pub enum NumberOrPercentage {
no_viewport_percentage!(NumberOrPercentage);
impl Parse for NumberOrPercentage {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
if let Ok(per) = input.try(Percentage::parse_non_negative) {
impl NumberOrPercentage {
fn parse_with_clamping_mode(context: &ParserContext,
input: &mut Parser,
type_: AllowedNumericType)
-> Result<Self, ()> {
if let Ok(per) = input.try(|i| Percentage::parse_with_clamping_mode(i, type_)) {
return Ok(NumberOrPercentage::Percentage(per));
}
Number::parse_non_negative(context, input).map(NumberOrPercentage::Number)
parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number)
}
/// Parse a non-negative number or percentage.
pub fn parse_non_negative(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
}
}
impl Parse for NumberOrPercentage {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
}
}