aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnthony Ramine <n.oxyde@gmail.com>2017-05-13 16:46:58 +0200
committerAnthony Ramine <n.oxyde@gmail.com>2017-05-15 15:36:26 +0200
commitea4e7299d40e788da18383767dc445b2482df824 (patch)
treeef1817a94f90a7658d977d5b5e37ffba08891b37
parent9e6f9db1275c7bfaeb3584be330e5e09ac4c3eab (diff)
downloadservo-ea4e7299d40e788da18383767dc445b2482df824.tar.gz
servo-ea4e7299d40e788da18383767dc445b2482df824.zip
Implement -webkit-gradient() (fixes #16542)
-rw-r--r--components/style/properties/longhand/border.mako.rs2
-rw-r--r--components/style/values/specified/image.rs264
-rw-r--r--components/style/values/specified/length.rs11
-rw-r--r--components/style/values/specified/mod.rs24
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)
}
}