mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Refactor Gradient
In a similar way to Position, now specified and computed gradients share a common Gradient type defined in style::values::generics::image. This allows us to reuse most code for many style traits like ToCss, HasViewportPercentage and ToComputedValue. The test changes are the fallout of the disappearance of AngleOrCorner::None, which align our code to the spec for serialisation, where components that can be omitted should be omitted.
This commit is contained in:
parent
f2aaba685b
commit
abef5da9d8
10 changed files with 742 additions and 888 deletions
|
@ -12,22 +12,56 @@ use cssparser::{Parser, Token};
|
|||
use parser::{Parse, ParserContext};
|
||||
#[cfg(feature = "servo")]
|
||||
use servo_url::ServoUrl;
|
||||
use std::f32::consts::PI;
|
||||
use std::fmt;
|
||||
use style_traits::ToCss;
|
||||
use values::generics::image::{CompatMode, ColorStop as GenericColorStop};
|
||||
use values::generics::image::{Gradient as GenericGradient, GradientItem as GenericGradientItem};
|
||||
use values::generics::image::{Circle, CompatMode, Ellipse, ColorStop as GenericColorStop};
|
||||
use values::generics::image::{EndingShape as GenericEndingShape, Gradient as GenericGradient};
|
||||
use values::generics::image::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind};
|
||||
use values::generics::image::{Image as GenericImage, ImageRect as GenericImageRect};
|
||||
use values::specified::{Angle, CSSColor, Length, LengthOrPercentage, NumberOrPercentage};
|
||||
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::specified::url::SpecifiedUrl;
|
||||
|
||||
/// Specified values for an image according to CSS-IMAGES.
|
||||
/// https://drafts.csswg.org/css-images/#image-values
|
||||
pub type Image = GenericImage<Gradient, NumberOrPercentage>;
|
||||
pub type Image = GenericImage<Gradient, ImageRect>;
|
||||
|
||||
/// Specified values for a CSS gradient.
|
||||
/// https://drafts.csswg.org/css-images/#gradients
|
||||
pub type Gradient = GenericGradient<GradientKind, CSSColor, LengthOrPercentage>;
|
||||
pub type Gradient = GenericGradient<
|
||||
LineDirection,
|
||||
Length,
|
||||
LengthOrPercentage,
|
||||
Position,
|
||||
CSSColor,
|
||||
>;
|
||||
|
||||
/// A specified gradient kind.
|
||||
pub type GradientKind = GenericGradientKind<
|
||||
LineDirection,
|
||||
Length,
|
||||
LengthOrPercentage,
|
||||
Position,
|
||||
>;
|
||||
|
||||
/// A specified gradient line direction.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
pub enum LineDirection {
|
||||
/// An angular direction.
|
||||
Angle(Angle),
|
||||
/// A horizontal direction.
|
||||
Horizontal(X),
|
||||
/// A vertical direction.
|
||||
Vertical(Y),
|
||||
/// A direction towards a corner of a box.
|
||||
Corner(X, Y),
|
||||
}
|
||||
|
||||
/// A specified ending shape.
|
||||
pub type EndingShape = GenericEndingShape<Length, LengthOrPercentage>;
|
||||
|
||||
/// A specified gradient item.
|
||||
pub type GradientItem = GenericGradientItem<CSSColor, LengthOrPercentage>;
|
||||
|
@ -77,103 +111,59 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToCss for Gradient {
|
||||
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||
if self.compat_mode == CompatMode::WebKit {
|
||||
try!(dest.write_str("-webkit-"));
|
||||
}
|
||||
if self.repeating {
|
||||
try!(dest.write_str("repeating-"));
|
||||
}
|
||||
let mut skipcomma = false;
|
||||
match self.kind {
|
||||
GradientKind::Linear(angle_or_corner) => {
|
||||
try!(dest.write_str("linear-gradient("));
|
||||
try!(angle_or_corner.to_css(dest, self.compat_mode));
|
||||
if angle_or_corner == AngleOrCorner::None {
|
||||
skipcomma = true;
|
||||
}
|
||||
},
|
||||
GradientKind::Radial(ref shape, ref position) => {
|
||||
try!(dest.write_str("radial-gradient("));
|
||||
if self.compat_mode == CompatMode::Modern {
|
||||
try!(shape.to_css(dest));
|
||||
try!(dest.write_str(" at "));
|
||||
try!(position.to_css(dest));
|
||||
} else {
|
||||
try!(position.to_css(dest));
|
||||
try!(dest.write_str(", "));
|
||||
try!(shape.to_css(dest));
|
||||
}
|
||||
},
|
||||
}
|
||||
for item in &self.items {
|
||||
if !skipcomma {
|
||||
try!(dest.write_str(", "));
|
||||
} else {
|
||||
skipcomma = false;
|
||||
}
|
||||
try!(item.to_css(dest));
|
||||
}
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Parses a gradient from the given arguments.
|
||||
pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Gradient, ()> {
|
||||
fn parse<F>(context: &ParserContext, input: &mut Parser, parse_kind: F)
|
||||
-> Result<(GradientKind, Vec<GradientItem>), ()>
|
||||
where F: FnOnce(&ParserContext, &mut Parser) -> Result<GradientKind, ()>
|
||||
{
|
||||
input.parse_nested_block(|input| {
|
||||
let kind = try!(parse_kind(context, input));
|
||||
let items = try!(Gradient::parse_items(context, input));
|
||||
Ok((kind, items))
|
||||
})
|
||||
};
|
||||
let mut repeating = false;
|
||||
let mut compat_mode = CompatMode::Modern;
|
||||
let (gradient_kind, items) = match_ignore_ascii_case! { &try!(input.expect_function()),
|
||||
enum Shape {
|
||||
Linear,
|
||||
Radial,
|
||||
}
|
||||
|
||||
let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &try!(input.expect_function()),
|
||||
"linear-gradient" => {
|
||||
try!(parse(context, input, GradientKind::parse_modern_linear))
|
||||
(Shape::Linear, false, CompatMode::Modern)
|
||||
},
|
||||
"-webkit-linear-gradient" => {
|
||||
compat_mode = CompatMode::WebKit;
|
||||
try!(parse(context, input, GradientKind::parse_webkit_linear))
|
||||
(Shape::Linear, false, CompatMode::WebKit)
|
||||
},
|
||||
"repeating-linear-gradient" => {
|
||||
repeating = true;
|
||||
try!(parse(context, input, GradientKind::parse_modern_linear))
|
||||
(Shape::Linear, true, CompatMode::Modern)
|
||||
},
|
||||
"-webkit-repeating-linear-gradient" => {
|
||||
repeating = true;
|
||||
compat_mode = CompatMode::WebKit;
|
||||
try!(parse(context, input, GradientKind::parse_webkit_linear))
|
||||
(Shape::Linear, true, CompatMode::WebKit)
|
||||
},
|
||||
"radial-gradient" => {
|
||||
try!(parse(context, input, GradientKind::parse_modern_radial))
|
||||
(Shape::Radial, false, CompatMode::Modern)
|
||||
},
|
||||
"-webkit-radial-gradient" => {
|
||||
compat_mode = CompatMode::WebKit;
|
||||
try!(parse(context, input, GradientKind::parse_webkit_radial))
|
||||
(Shape::Radial, false, CompatMode::WebKit)
|
||||
},
|
||||
"repeating-radial-gradient" => {
|
||||
repeating = true;
|
||||
try!(parse(context, input, GradientKind::parse_modern_radial))
|
||||
(Shape::Radial, true, CompatMode::Modern)
|
||||
},
|
||||
"-webkit-repeating-radial-gradient" => {
|
||||
repeating = true;
|
||||
compat_mode = CompatMode::WebKit;
|
||||
try!(parse(context, input, GradientKind::parse_webkit_radial))
|
||||
(Shape::Radial, true, CompatMode::WebKit)
|
||||
},
|
||||
_ => { return Err(()); }
|
||||
};
|
||||
|
||||
let (kind, items) = input.parse_nested_block(|i| {
|
||||
let shape = match shape {
|
||||
Shape::Linear => GradientKind::parse_linear(context, i, compat_mode)?,
|
||||
Shape::Radial => GradientKind::parse_radial(context, i, compat_mode)?,
|
||||
};
|
||||
let items = Gradient::parse_items(context, i)?;
|
||||
Ok((shape, items))
|
||||
})?;
|
||||
|
||||
if items.len() < 2 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Gradient {
|
||||
items: items,
|
||||
repeating: repeating,
|
||||
kind: gradient_kind,
|
||||
kind: kind,
|
||||
compat_mode: compat_mode,
|
||||
})
|
||||
}
|
||||
|
@ -197,167 +187,205 @@ impl Gradient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Specified values for CSS linear or radial gradients.
|
||||
/// https://drafts.csswg.org/css-images/#gradients
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
pub enum GradientKind {
|
||||
/// A `<linear-gradient()>`:
|
||||
///
|
||||
/// https://drafts.csswg.org/css-images/#funcdef-linear-gradient
|
||||
Linear(AngleOrCorner),
|
||||
impl GradientKind {
|
||||
fn parse_linear(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
compat_mode: CompatMode)
|
||||
-> Result<Self, ()> {
|
||||
let direction = if let Ok(d) = input.try(|i| LineDirection::parse(context, i, compat_mode)) {
|
||||
input.expect_comma()?;
|
||||
d
|
||||
} else {
|
||||
LineDirection::Vertical(Y::Bottom)
|
||||
};
|
||||
Ok(GenericGradientKind::Linear(direction))
|
||||
}
|
||||
|
||||
/// A `<radial-gradient()>`:
|
||||
///
|
||||
/// https://drafts.csswg.org/css-images/#radial-gradients
|
||||
Radial(EndingShape, Position),
|
||||
fn parse_radial(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
compat_mode: CompatMode)
|
||||
-> Result<Self, ()> {
|
||||
let (shape, position) = if compat_mode == CompatMode::Modern {
|
||||
let shape = input.try(|i| EndingShape::parse(context, i, compat_mode));
|
||||
let position = input.try(|i| {
|
||||
i.expect_ident_matching("at")?;
|
||||
Position::parse(context, i)
|
||||
});
|
||||
(shape, position)
|
||||
} else {
|
||||
let position = input.try(|i| Position::parse(context, i));
|
||||
let shape = input.try(|i| {
|
||||
if position.is_ok() {
|
||||
i.expect_comma()?;
|
||||
}
|
||||
EndingShape::parse(context, i, compat_mode)
|
||||
});
|
||||
(shape, position)
|
||||
};
|
||||
|
||||
if shape.is_ok() || position.is_ok() {
|
||||
input.expect_comma()?;
|
||||
}
|
||||
|
||||
let shape = shape.unwrap_or({
|
||||
GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner))
|
||||
});
|
||||
let position = position.unwrap_or(Position::center());
|
||||
Ok(GenericGradientKind::Radial(shape, position))
|
||||
}
|
||||
}
|
||||
|
||||
impl GradientKind {
|
||||
/// Parses a linear gradient kind from the given arguments.
|
||||
fn parse_modern_linear(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
|
||||
let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
|
||||
try!(input.expect_comma());
|
||||
AngleOrCorner::Angle(angle)
|
||||
} else {
|
||||
if input.try(|i| i.expect_ident_matching("to")).is_ok() {
|
||||
let (horizontal, vertical) =
|
||||
if let Ok(value) = input.try(X::parse) {
|
||||
(Some(value), input.try(Y::parse).ok())
|
||||
} else {
|
||||
let value = try!(Y::parse(input));
|
||||
(input.try(X::parse).ok(), Some(value))
|
||||
};
|
||||
try!(input.expect_comma());
|
||||
AngleOrCorner::Corner(horizontal, vertical)
|
||||
} else {
|
||||
AngleOrCorner::None
|
||||
}
|
||||
};
|
||||
Ok(GradientKind::Linear(direction))
|
||||
impl GenericsLineDirection for LineDirection {
|
||||
fn points_downwards(&self) -> bool {
|
||||
match *self {
|
||||
LineDirection::Angle(ref angle) => angle.radians() == PI,
|
||||
LineDirection::Vertical(Y::Bottom) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_webkit_linear(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
|
||||
let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
|
||||
AngleOrCorner::Angle(angle)
|
||||
} else {
|
||||
if let Ok(value) = input.try(X::parse) {
|
||||
AngleOrCorner::Corner(Some(value), input.try(Y::parse).ok())
|
||||
} else {
|
||||
if let Ok(value) = input.try(Y::parse) {
|
||||
AngleOrCorner::Corner(input.try(X::parse).ok(), Some(value))
|
||||
} else {
|
||||
AngleOrCorner::None
|
||||
fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
|
||||
where W: fmt::Write
|
||||
{
|
||||
match *self {
|
||||
LineDirection::Angle(angle) => {
|
||||
angle.to_css(dest)
|
||||
},
|
||||
LineDirection::Horizontal(x) => {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
dest.write_str("to ")?;
|
||||
}
|
||||
x.to_css(dest)
|
||||
},
|
||||
LineDirection::Vertical(y) => {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
dest.write_str("to ")?;
|
||||
}
|
||||
y.to_css(dest)
|
||||
},
|
||||
LineDirection::Corner(x, y) => {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
dest.write_str("to ")?;
|
||||
}
|
||||
x.to_css(dest)?;
|
||||
dest.write_str(" ")?;
|
||||
y.to_css(dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LineDirection {
|
||||
fn parse(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
compat_mode: CompatMode)
|
||||
-> Result<Self, ()> {
|
||||
if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) {
|
||||
return Ok(LineDirection::Angle(angle));
|
||||
}
|
||||
input.try(|i| {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
i.expect_ident_matching("to")?;
|
||||
}
|
||||
if let Ok(x) = i.try(X::parse) {
|
||||
if let Ok(y) = i.try(Y::parse) {
|
||||
return Ok(LineDirection::Corner(x, y));
|
||||
}
|
||||
return Ok(LineDirection::Horizontal(x));
|
||||
}
|
||||
let y = Y::parse(i)?;
|
||||
if let Ok(x) = i.try(X::parse) {
|
||||
return Ok(LineDirection::Corner(x, y));
|
||||
}
|
||||
Ok(LineDirection::Vertical(y))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EndingShape {
|
||||
fn parse(context: &ParserContext,
|
||||
input: &mut Parser,
|
||||
compat_mode: CompatMode)
|
||||
-> Result<Self, ()> {
|
||||
if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
|
||||
if input.try(|i| i.expect_ident_matching("circle")).is_ok() {
|
||||
return Ok(GenericEndingShape::Circle(Circle::Extent(extent)));
|
||||
}
|
||||
let _ = input.try(|i| i.expect_ident_matching("ellipse"));
|
||||
return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(extent)));
|
||||
}
|
||||
if input.try(|i| i.expect_ident_matching("circle")).is_ok() {
|
||||
if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
|
||||
return Ok(GenericEndingShape::Circle(Circle::Extent(extent)));
|
||||
}
|
||||
if compat_mode == CompatMode::Modern {
|
||||
if let Ok(length) = input.try(|i| Length::parse(context, i)) {
|
||||
return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
|
||||
}
|
||||
}
|
||||
};
|
||||
if direction != AngleOrCorner::None {
|
||||
try!(input.expect_comma());
|
||||
return Ok(GenericEndingShape::Circle(Circle::Extent(ShapeExtent::FarthestCorner)));
|
||||
}
|
||||
Ok(GradientKind::Linear(direction))
|
||||
}
|
||||
|
||||
/// Parses a modern radial gradient from the given arguments.
|
||||
pub fn parse_modern_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
|
||||
let mut needs_comma = true;
|
||||
|
||||
// Ending shape and position can be in various order. Checks all probabilities.
|
||||
let (shape, position) = if let Ok(position) = input.try(|i| parse_position(context, i)) {
|
||||
// Handle just <position>
|
||||
(EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)), position)
|
||||
} else if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) {
|
||||
// Handle <LengthOrPercentage> <LengthOrPercentage> <shape>? <position>?
|
||||
let _ = input.try(|input| input.expect_ident_matching("ellipse"));
|
||||
(EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second)),
|
||||
input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
|
||||
} else if let Ok(length) = input.try(|i| Length::parse(context, i)) {
|
||||
// Handle <Length> <circle>? <position>?
|
||||
let _ = input.try(|input| input.expect_ident_matching("circle"));
|
||||
(EndingShape::Circle(LengthOrKeyword::Length(length)),
|
||||
input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
|
||||
} else if let Ok(keyword) = input.try(SizeKeyword::parse_modern) {
|
||||
// Handle <keyword> <shape-keyword>? <position>?
|
||||
let shape = if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
|
||||
EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
|
||||
if input.try(|i| i.expect_ident_matching("ellipse")).is_ok() {
|
||||
if let Ok(extent) = input.try(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) {
|
||||
return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(extent)));
|
||||
}
|
||||
if compat_mode == CompatMode::Modern {
|
||||
let pair: Result<_, ()> = input.try(|i| {
|
||||
let x = LengthOrPercentage::parse(context, i)?;
|
||||
let y = LengthOrPercentage::parse(context, i)?;
|
||||
Ok((x, y))
|
||||
});
|
||||
if let Ok((x, y)) = pair {
|
||||
return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x, y)));
|
||||
}
|
||||
}
|
||||
return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)));
|
||||
}
|
||||
if let Ok(length) = input.try(|i| Length::parse(context, i)) {
|
||||
if let Ok(y) = input.try(|i| LengthOrPercentage::parse(context, i)) {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
let _ = input.try(|i| i.expect_ident_matching("ellipse"));
|
||||
}
|
||||
return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
|
||||
}
|
||||
if compat_mode == CompatMode::Modern {
|
||||
let y = input.try(|i| {
|
||||
i.expect_ident_matching("ellipse")?;
|
||||
LengthOrPercentage::parse(context, i)
|
||||
});
|
||||
if let Ok(y) = y {
|
||||
return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
|
||||
}
|
||||
let _ = input.try(|i| i.expect_ident_matching("circle"));
|
||||
}
|
||||
return Ok(GenericEndingShape::Circle(Circle::Radius(length)));
|
||||
}
|
||||
input.try(|i| {
|
||||
let x = Percentage::parse(context, i)?;
|
||||
let y = if let Ok(y) = i.try(|i| LengthOrPercentage::parse(context, i)) {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
let _ = i.try(|i| i.expect_ident_matching("ellipse"));
|
||||
}
|
||||
y
|
||||
} else {
|
||||
let _ = input.try(|input| input.expect_ident_matching("ellipse"));
|
||||
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
|
||||
if compat_mode == CompatMode::Modern {
|
||||
i.expect_ident_matching("ellipse")?;
|
||||
}
|
||||
LengthOrPercentage::parse(context, i)?
|
||||
};
|
||||
(shape, input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
|
||||
} else {
|
||||
// Handle <shape-keyword> <length>? <position>?
|
||||
if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() {
|
||||
// Handle <ellipse> <LengthOrPercentageOrKeyword>? <position>?
|
||||
let length = input.try(|i| LengthOrPercentageOrKeyword::parse(context, i, SizeKeyword::parse_modern))
|
||||
.unwrap_or(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner));
|
||||
(EndingShape::Ellipse(length),
|
||||
input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
|
||||
} else if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
|
||||
// Handle <ellipse> <LengthOrKeyword>? <position>?
|
||||
let length = input.try(|i| LengthOrKeyword::parse(context, i, SizeKeyword::parse_modern))
|
||||
.unwrap_or(LengthOrKeyword::Keyword(SizeKeyword::FarthestCorner));
|
||||
(EndingShape::Circle(length), input.try(|i| parse_position(context, i))
|
||||
.unwrap_or(Position::center()))
|
||||
} else {
|
||||
// If there is no shape keyword, it should set to default.
|
||||
needs_comma = false;
|
||||
(EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)),
|
||||
input.try(|i| parse_position(context, i)).unwrap_or(Position::center()))
|
||||
}
|
||||
};
|
||||
|
||||
if needs_comma {
|
||||
try!(input.expect_comma());
|
||||
}
|
||||
|
||||
Ok(GradientKind::Radial(shape, position))
|
||||
Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x.into(), y)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a webkit radial gradient from the given arguments.
|
||||
/// https://compat.spec.whatwg.org/#css-gradients-webkit-radial-gradient
|
||||
pub fn parse_webkit_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> {
|
||||
let position = if let Ok(position) = input.try(|i| Position::parse(context, i)) {
|
||||
try!(input.expect_comma());
|
||||
position
|
||||
} else {
|
||||
Position::center()
|
||||
};
|
||||
|
||||
let mut needs_comma = true;
|
||||
|
||||
// Ending shape and position can be in various order. Checks all probabilities.
|
||||
let shape = if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) {
|
||||
EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second))
|
||||
} else if let Ok(keyword) = input.try(SizeKeyword::parse) {
|
||||
// Handle <keyword> <shape-keyword>?
|
||||
if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
|
||||
EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
|
||||
} else {
|
||||
let _ = input.try(|input| input.expect_ident_matching("ellipse"));
|
||||
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
|
||||
}
|
||||
} else {
|
||||
// Handle <shape-keyword> <keyword>?
|
||||
if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() {
|
||||
// Handle <ellipse> <keyword>?
|
||||
let keyword = input.try(SizeKeyword::parse).unwrap_or((SizeKeyword::Cover));
|
||||
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword))
|
||||
} else if input.try(|input| input.expect_ident_matching("circle")).is_ok() {
|
||||
// Handle <circle> <keyword>?
|
||||
let keyword = input.try(SizeKeyword::parse).unwrap_or((SizeKeyword::Cover));
|
||||
EndingShape::Circle(LengthOrKeyword::Keyword(keyword))
|
||||
} else {
|
||||
// If there is no shape keyword, it should set to default.
|
||||
needs_comma = false;
|
||||
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::Cover))
|
||||
}
|
||||
};
|
||||
|
||||
if needs_comma {
|
||||
try!(input.expect_comma());
|
||||
impl ShapeExtent {
|
||||
fn parse_with_compat_mode(input: &mut Parser,
|
||||
compat_mode: CompatMode)
|
||||
-> Result<Self, ()> {
|
||||
match try!(Self::parse(input)) {
|
||||
ShapeExtent::Contain | ShapeExtent::Cover if compat_mode == CompatMode::Modern => Err(()),
|
||||
keyword => Ok(keyword),
|
||||
}
|
||||
|
||||
Ok(GradientKind::Radial(shape, position))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,54 +418,6 @@ impl Parse for ImageRect {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_two_length(context: &ParserContext, input: &mut Parser)
|
||||
-> Result<(LengthOrPercentage, LengthOrPercentage), ()> {
|
||||
let first = try!(LengthOrPercentage::parse(context, input));
|
||||
let second = try!(LengthOrPercentage::parse(context, input));
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
fn parse_position(context: &ParserContext, input: &mut Parser) -> Result<Position, ()> {
|
||||
try!(input.expect_ident_matching("at"));
|
||||
input.try(|i| Position::parse(context, i))
|
||||
}
|
||||
|
||||
/// Specified values for an angle or a corner in a linear gradient.
|
||||
#[derive(Clone, PartialEq, Copy, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AngleOrCorner {
|
||||
Angle(Angle),
|
||||
Corner(Option<X>, Option<Y>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl AngleOrCorner {
|
||||
fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result where W: fmt::Write {
|
||||
match *self {
|
||||
AngleOrCorner::None => Ok(()),
|
||||
AngleOrCorner::Angle(angle) => angle.to_css(dest),
|
||||
AngleOrCorner::Corner(horizontal, vertical) => {
|
||||
if compat_mode == CompatMode::Modern {
|
||||
try!(dest.write_str("to "));
|
||||
}
|
||||
let mut horizontal_present = false;
|
||||
if let Some(horizontal) = horizontal {
|
||||
try!(horizontal.to_css(dest));
|
||||
horizontal_present = true;
|
||||
}
|
||||
if let Some(vertical) = vertical {
|
||||
if horizontal_present {
|
||||
try!(dest.write_str(" "));
|
||||
}
|
||||
try!(vertical.to_css(dest));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ColorStop {
|
||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||
Ok(ColorStop {
|
||||
|
@ -447,115 +427,6 @@ impl Parse for ColorStop {
|
|||
}
|
||||
}
|
||||
|
||||
/// Determines whether the gradient's ending shape is a circle or an ellipse.
|
||||
/// If <shape> is omitted, the ending shape defaults to a circle
|
||||
/// if the <size> is a single <length>, and to an ellipse otherwise.
|
||||
/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-ending-shape
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
pub enum EndingShape {
|
||||
Circle(LengthOrKeyword),
|
||||
Ellipse(LengthOrPercentageOrKeyword),
|
||||
}
|
||||
|
||||
impl ToCss for EndingShape {
|
||||
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||
match *self {
|
||||
EndingShape::Circle(ref length) => {
|
||||
try!(dest.write_str("circle "));
|
||||
try!(length.to_css(dest));
|
||||
},
|
||||
EndingShape::Ellipse(ref length) => {
|
||||
try!(dest.write_str("ellipse "));
|
||||
try!(length.to_css(dest));
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
pub enum LengthOrKeyword {
|
||||
Length(Length),
|
||||
Keyword(SizeKeyword),
|
||||
}
|
||||
|
||||
impl LengthOrKeyword {
|
||||
fn parse<F>(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result<Self, ()>
|
||||
where F: Fn(&mut Parser) -> Result<SizeKeyword, ()>
|
||||
{
|
||||
if let Ok(keyword) = input.try(parse_size_keyword) {
|
||||
Ok(LengthOrKeyword::Keyword(keyword))
|
||||
} else {
|
||||
Ok(LengthOrKeyword::Length(try!(Length::parse(context, input))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for LengthOrKeyword {
|
||||
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||
match *self {
|
||||
LengthOrKeyword::Length(ref length) => length.to_css(dest),
|
||||
LengthOrKeyword::Keyword(keyword) => keyword.to_css(dest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
pub enum LengthOrPercentageOrKeyword {
|
||||
LengthOrPercentage(LengthOrPercentage, LengthOrPercentage),
|
||||
Keyword(SizeKeyword),
|
||||
}
|
||||
|
||||
|
||||
impl LengthOrPercentageOrKeyword {
|
||||
fn parse<F>(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result<Self, ()>
|
||||
where F: Fn(&mut Parser) -> Result<SizeKeyword, ()>
|
||||
{
|
||||
if let Ok(keyword) = input.try(parse_size_keyword) {
|
||||
Ok(LengthOrPercentageOrKeyword::Keyword(keyword))
|
||||
} else {
|
||||
Ok(LengthOrPercentageOrKeyword::LengthOrPercentage(
|
||||
try!(LengthOrPercentage::parse(context, input)),
|
||||
try!(LengthOrPercentage::parse(context, input))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for LengthOrPercentageOrKeyword {
|
||||
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||
match *self {
|
||||
LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, ref second_len) => {
|
||||
try!(first_len.to_css(dest));
|
||||
try!(dest.write_str(" "));
|
||||
second_len.to_css(dest)
|
||||
},
|
||||
LengthOrPercentageOrKeyword::Keyword(keyword) => keyword.to_css(dest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-images/#typedef-extent-keyword
|
||||
define_css_keyword_enum!(SizeKeyword: "closest-side" => ClosestSide, "farthest-side" => FarthestSide,
|
||||
"closest-corner" => ClosestCorner, "farthest-corner" => FarthestCorner,
|
||||
"contain" => Contain, "cover" => Cover);
|
||||
|
||||
impl SizeKeyword {
|
||||
fn parse_modern(input: &mut Parser) -> Result<Self, ()> {
|
||||
match try!(SizeKeyword::parse(input)) {
|
||||
SizeKeyword::Contain | SizeKeyword::Cover => Err(()),
|
||||
keyword => Ok(keyword),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specified values for none | <image> | <mask-source>.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue