diff options
author | Anthony Ramine <n.oxyde@gmail.com> | 2017-05-13 16:46:58 +0200 |
---|---|---|
committer | Anthony Ramine <n.oxyde@gmail.com> | 2017-05-15 15:36:26 +0200 |
commit | ea4e7299d40e788da18383767dc445b2482df824 (patch) | |
tree | ef1817a94f90a7658d977d5b5e37ffba08891b37 | |
parent | 9e6f9db1275c7bfaeb3584be330e5e09ac4c3eab (diff) | |
download | servo-ea4e7299d40e788da18383767dc445b2482df824.tar.gz servo-ea4e7299d40e788da18383767dc445b2482df824.zip |
Implement -webkit-gradient() (fixes #16542)
-rw-r--r-- | components/style/properties/longhand/border.mako.rs | 2 | ||||
-rw-r--r-- | components/style/values/specified/image.rs | 264 | ||||
-rw-r--r-- | components/style/values/specified/length.rs | 11 | ||||
-rw-r--r-- | components/style/values/specified/mod.rs | 24 |
4 files changed, 285 insertions, 16 deletions
diff --git a/components/style/properties/longhand/border.mako.rs b/components/style/properties/longhand/border.mako.rs index 86cf3bd0e7f..f18ae154428 100644 --- a/components/style/properties/longhand/border.mako.rs +++ b/components/style/properties/longhand/border.mako.rs @@ -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, diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index f85535d011d..dcbc5db1c5c 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -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, diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 27e5494b9f5..7b386297ed2 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -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) } } diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 70e0ea75e8d..0bd876a190d 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -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) } } |