mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Implement -webkit-gradient() (fixes #16542)
This commit is contained in:
parent
9e6f9db127
commit
ea4e7299d4
4 changed files with 285 additions and 16 deletions
|
@ -717,7 +717,7 @@ ${helpers.predefined_type("border-image-source", "ImageLayer",
|
||||||
|
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
for _ in 0..4 {
|
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 {
|
match value {
|
||||||
Ok(val) => values.push(val),
|
Ok(val) => values.push(val),
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
|
|
|
@ -12,6 +12,7 @@ use cssparser::{Parser, Token};
|
||||||
use parser::{Parse, ParserContext};
|
use parser::{Parse, ParserContext};
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use style_traits::ToCss;
|
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::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind};
|
||||||
use values::generics::image::{Image as GenericImage, ImageRect as GenericImageRect};
|
use values::generics::image::{Image as GenericImage, ImageRect as GenericImageRect};
|
||||||
use values::generics::image::{LineDirection as GenericsLineDirection, ShapeExtent};
|
use values::generics::image::{LineDirection as GenericsLineDirection, ShapeExtent};
|
||||||
use values::specified::{Angle, CSSColor, Length, LengthOrPercentage, NumberOrPercentage, Percentage};
|
use values::generics::position::Position as GenericPosition;
|
||||||
use values::specified::position::{Position, X, Y};
|
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;
|
use values::specified::url::SpecifiedUrl;
|
||||||
|
|
||||||
/// A specified image layer.
|
/// A specified image layer.
|
||||||
|
@ -145,6 +148,9 @@ impl Parse for Gradient {
|
||||||
"-webkit-repeating-radial-gradient" => {
|
"-webkit-repeating-radial-gradient" => {
|
||||||
(Shape::Radial, true, CompatMode::WebKit)
|
(Shape::Radial, true, CompatMode::WebKit)
|
||||||
},
|
},
|
||||||
|
"-webkit-gradient" => {
|
||||||
|
return input.parse_nested_block(|i| Self::parse_webkit_gradient_argument(context, i));
|
||||||
|
},
|
||||||
_ => { return Err(()); }
|
_ => { 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 {
|
impl GradientKind {
|
||||||
fn parse_linear(context: &ParserContext,
|
fn parse_linear(context: &ParserContext,
|
||||||
input: &mut Parser,
|
input: &mut Parser,
|
||||||
|
@ -408,13 +660,13 @@ impl Parse for ImageRect {
|
||||||
let string = i.expect_url_or_string()?;
|
let string = i.expect_url_or_string()?;
|
||||||
let url = SpecifiedUrl::parse_from_string(string, context)?;
|
let url = SpecifiedUrl::parse_from_string(string, context)?;
|
||||||
i.expect_comma()?;
|
i.expect_comma()?;
|
||||||
let top = NumberOrPercentage::parse(context, i)?;
|
let top = NumberOrPercentage::parse_non_negative(context, i)?;
|
||||||
i.expect_comma()?;
|
i.expect_comma()?;
|
||||||
let right = NumberOrPercentage::parse(context, i)?;
|
let right = NumberOrPercentage::parse_non_negative(context, i)?;
|
||||||
i.expect_comma()?;
|
i.expect_comma()?;
|
||||||
let bottom = NumberOrPercentage::parse(context, i)?;
|
let bottom = NumberOrPercentage::parse_non_negative(context, i)?;
|
||||||
i.expect_comma()?;
|
i.expect_comma()?;
|
||||||
let left = NumberOrPercentage::parse(context, i)?;
|
let left = NumberOrPercentage::parse_non_negative(context, i)?;
|
||||||
|
|
||||||
Ok(ImageRect {
|
Ok(ImageRect {
|
||||||
url: url,
|
url: url,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use std::{cmp, fmt, mem};
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::ops::Mul;
|
use std::ops::Mul;
|
||||||
use style_traits::ToCss;
|
use style_traits::ToCss;
|
||||||
use style_traits::values::specified::AllowedLengthType;
|
use style_traits::values::specified::{AllowedLengthType, AllowedNumericType};
|
||||||
use stylesheets::CssRuleType;
|
use stylesheets::CssRuleType;
|
||||||
use super::{AllowQuirks, Number, ToComputedValue};
|
use super::{AllowQuirks, Number, ToComputedValue};
|
||||||
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
|
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
|
||||||
|
@ -729,7 +729,10 @@ impl ToCss for Percentage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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()) {
|
match try!(input.next()) {
|
||||||
Token::Percentage(ref value) if context.is_ok(value.unit_value) => {
|
Token::Percentage(ref value) if context.is_ok(value.unit_value) => {
|
||||||
Ok(Percentage(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.
|
/// Parses a percentage token, but rejects it if it's negative.
|
||||||
pub fn parse_non_negative(input: &mut Parser) -> Result<Self, ()> {
|
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 {
|
impl Parse for Percentage {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||||
Self::parse_internal(input, AllowedLengthType::All)
|
Self::parse_with_clamping_mode(input, AllowedNumericType::All)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -658,7 +658,7 @@ impl ToCss for Number {
|
||||||
|
|
||||||
/// <number-percentage>
|
/// <number-percentage>
|
||||||
/// Accepts only non-negative numbers.
|
/// Accepts only non-negative numbers.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum NumberOrPercentage {
|
pub enum NumberOrPercentage {
|
||||||
|
@ -668,13 +668,27 @@ pub enum NumberOrPercentage {
|
||||||
|
|
||||||
no_viewport_percentage!(NumberOrPercentage);
|
no_viewport_percentage!(NumberOrPercentage);
|
||||||
|
|
||||||
impl Parse for NumberOrPercentage {
|
impl NumberOrPercentage {
|
||||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
fn parse_with_clamping_mode(context: &ParserContext,
|
||||||
if let Ok(per) = input.try(Percentage::parse_non_negative) {
|
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));
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue