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:
Anthony Ramine 2017-05-12 17:28:05 +02:00
parent f2aaba685b
commit abef5da9d8
10 changed files with 742 additions and 888 deletions

View file

@ -58,11 +58,12 @@ use style::properties::longhands::border_image_repeat::computed_value::RepeatKey
use style::properties::style_structs; use style::properties::style_structs;
use style::servo::restyle_damage::REPAINT; use style::servo::restyle_damage::REPAINT;
use style::values::{Either, RGBA}; use style::values::{Either, RGBA};
use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind, LengthOrPercentage}; use style::values::computed::{Gradient, GradientItem, LengthOrPercentage};
use style::values::computed::{LengthOrPercentageOrAuto, LengthOrKeyword, LengthOrPercentageOrKeyword}; use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position};
use style::values::computed::{NumberOrPercentage, Position}; use style::values::computed::image::{EndingShape, LineDirection};
use style::values::computed::image::{EndingShape, SizeKeyword}; use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape};
use style::values::generics::image::{GradientItem as GenericGradientItem, Image}; use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind};
use style::values::generics::image::{Image, ShapeExtent};
use style::values::specified::position::{X, Y}; use style::values::specified::position::{X, Y};
use style_traits::CSSPixel; use style_traits::CSSPixel;
use style_traits::cursor::Cursor; use style_traits::cursor::Cursor;
@ -391,7 +392,7 @@ pub trait FragmentDisplayListBuilding {
fn convert_linear_gradient(&self, fn convert_linear_gradient(&self,
bounds: &Rect<Au>, bounds: &Rect<Au>,
stops: &[GradientItem], stops: &[GradientItem],
angle_or_corner: &AngleOrCorner, direction: &LineDirection,
repeating: bool, repeating: bool,
style: &ServoComputedValues) style: &ServoComputedValues)
-> display_list::Gradient; -> display_list::Gradient;
@ -759,36 +760,46 @@ fn get_ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Siz
/// Determines the radius of a circle if it was not explictly provided. /// Determines the radius of a circle if it was not explictly provided.
/// https://drafts.csswg.org/css-images-3/#typedef-size /// https://drafts.csswg.org/css-images-3/#typedef-size
fn convert_circle_size_keyword(keyword: SizeKeyword, fn convert_circle_size_keyword(keyword: ShapeExtent,
size: &Size2D<Au>, size: &Size2D<Au>,
center: &Point2D<Au>) -> Size2D<Au> { center: &Point2D<Au>) -> Size2D<Au> {
use style::values::computed::image::SizeKeyword::*;
let radius = match keyword { let radius = match keyword {
ClosestSide | Contain => { ShapeExtent::ClosestSide | ShapeExtent::Contain => {
let dist = get_distance_to_sides(size, center, ::std::cmp::min); let dist = get_distance_to_sides(size, center, ::std::cmp::min);
::std::cmp::min(dist.width, dist.height) ::std::cmp::min(dist.width, dist.height)
} }
FarthestSide => { ShapeExtent::FarthestSide => {
let dist = get_distance_to_sides(size, center, ::std::cmp::max); let dist = get_distance_to_sides(size, center, ::std::cmp::max);
::std::cmp::max(dist.width, dist.height) ::std::cmp::max(dist.width, dist.height)
} }
ClosestCorner => get_distance_to_corner(size, center, ::std::cmp::min), ShapeExtent::ClosestCorner => {
FarthestCorner | Cover => get_distance_to_corner(size, center, ::std::cmp::max), get_distance_to_corner(size, center, ::std::cmp::min)
},
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
get_distance_to_corner(size, center, ::std::cmp::max)
},
}; };
Size2D::new(radius, radius) Size2D::new(radius, radius)
} }
/// Determines the radius of an ellipse if it was not explictly provided. /// Determines the radius of an ellipse if it was not explictly provided.
/// https://drafts.csswg.org/css-images-3/#typedef-size /// https://drafts.csswg.org/css-images-3/#typedef-size
fn convert_ellipse_size_keyword(keyword: SizeKeyword, fn convert_ellipse_size_keyword(keyword: ShapeExtent,
size: &Size2D<Au>, size: &Size2D<Au>,
center: &Point2D<Au>) -> Size2D<Au> { center: &Point2D<Au>) -> Size2D<Au> {
use style::values::computed::image::SizeKeyword::*;
match keyword { match keyword {
ClosestSide | Contain => get_distance_to_sides(size, center, ::std::cmp::min), ShapeExtent::ClosestSide | ShapeExtent::Contain => {
FarthestSide => get_distance_to_sides(size, center, ::std::cmp::max), get_distance_to_sides(size, center, ::std::cmp::min)
ClosestCorner => get_ellipse_radius(size, center, ::std::cmp::min), },
FarthestCorner | Cover => get_ellipse_radius(size, center, ::std::cmp::max), ShapeExtent::FarthestSide => {
get_distance_to_sides(size, center, ::std::cmp::max)
},
ShapeExtent::ClosestCorner => {
get_ellipse_radius(size, center, ::std::cmp::min)
},
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
get_ellipse_radius(size, center, ::std::cmp::max)
},
} }
} }
@ -1098,13 +1109,13 @@ impl FragmentDisplayListBuilding for Fragment {
fn convert_linear_gradient(&self, fn convert_linear_gradient(&self,
bounds: &Rect<Au>, bounds: &Rect<Au>,
stops: &[GradientItem], stops: &[GradientItem],
angle_or_corner: &AngleOrCorner, direction: &LineDirection,
repeating: bool, repeating: bool,
style: &ServoComputedValues) style: &ServoComputedValues)
-> display_list::Gradient { -> display_list::Gradient {
let angle = match *angle_or_corner { let angle = match *direction {
AngleOrCorner::Angle(angle) => angle.radians(), LineDirection::Angle(angle) => angle.radians(),
AngleOrCorner::Corner(horizontal, vertical) => { LineDirection::Corner(horizontal, vertical) => {
// This the angle for one of the diagonals of the box. Our angle // This the angle for one of the diagonals of the box. Our angle
// will either be this one, this one + PI, or one of the other // will either be this one, this one + PI, or one of the other
// two perpendicular angles. // two perpendicular angles.
@ -1171,16 +1182,18 @@ impl FragmentDisplayListBuilding for Fragment {
let center = Point2D::new(specified(center.horizontal, bounds.size.width), let center = Point2D::new(specified(center.horizontal, bounds.size.width),
specified(center.vertical, bounds.size.height)); specified(center.vertical, bounds.size.height));
let radius = match *shape { let radius = match *shape {
EndingShape::Circle(LengthOrKeyword::Length(length)) GenericEndingShape::Circle(Circle::Radius(length)) => {
=> Size2D::new(length, length), Size2D::new(length, length)
EndingShape::Circle(LengthOrKeyword::Keyword(word)) },
=> convert_circle_size_keyword(word, &bounds.size, &center), GenericEndingShape::Circle(Circle::Extent(extent)) => {
EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(horizontal, convert_circle_size_keyword(extent, &bounds.size, &center)
vertical)) },
=> Size2D::new(specified(horizontal, bounds.size.width), GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
specified(vertical, bounds.size.height)), Size2D::new(specified(x, bounds.size.width), specified(y, bounds.size.height))
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(word)) },
=> convert_ellipse_size_keyword(word, &bounds.size, &center), GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
convert_ellipse_size_keyword(extent, &bounds.size, &center)
},
}; };
let mut stops = convert_gradient_stops(stops, radius.width, style); let mut stops = convert_gradient_stops(stops, radius.width, style);

View file

@ -192,9 +192,8 @@ impl nsStyleImage {
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE}; use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE}; use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE};
use gecko_bindings::structs::nsStyleCoord; use gecko_bindings::structs::nsStyleCoord;
use values::computed::{AngleOrCorner, GradientKind, GradientShape, LengthOrKeyword}; use values::computed::image::LineDirection;
use values::computed::LengthOrPercentageOrKeyword; use values::generics::image::{Circle, Ellipse, EndingShape, GradientKind, ShapeExtent};
use values::specified::SizeKeyword;
use values::specified::position::{X, Y}; use values::specified::position::{X, Y};
let stop_count = gradient.items.len(); let stop_count = gradient.items.len();
@ -204,7 +203,7 @@ impl nsStyleImage {
} }
let gecko_gradient = match gradient.kind { let gecko_gradient = match gradient.kind {
GradientKind::Linear(angle_or_corner) => { GradientKind::Linear(direction) => {
let gecko_gradient = unsafe { let gecko_gradient = unsafe {
Gecko_CreateGradient(NS_STYLE_GRADIENT_SHAPE_LINEAR as u8, Gecko_CreateGradient(NS_STYLE_GRADIENT_SHAPE_LINEAR as u8,
NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as u8, NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as u8,
@ -213,15 +212,15 @@ impl nsStyleImage {
stop_count as u32) stop_count as u32)
}; };
match angle_or_corner { match direction {
AngleOrCorner::Angle(angle) => { LineDirection::Angle(angle) => {
unsafe { unsafe {
(*gecko_gradient).mAngle.set(angle); (*gecko_gradient).mAngle.set(angle);
(*gecko_gradient).mBgPosX.set_value(CoordDataValue::None); (*gecko_gradient).mBgPosX.set_value(CoordDataValue::None);
(*gecko_gradient).mBgPosY.set_value(CoordDataValue::None); (*gecko_gradient).mBgPosY.set_value(CoordDataValue::None);
} }
}, },
AngleOrCorner::Corner(horiz, vert) => { LineDirection::Corner(horiz, vert) => {
let percent_x = match horiz { let percent_x = match horiz {
X::Left => 0.0, X::Left => 0.0,
X::Right => 1.0, X::Right => 1.0,
@ -245,28 +244,28 @@ impl nsStyleImage {
GradientKind::Radial(shape, position) => { GradientKind::Radial(shape, position) => {
let keyword_to_gecko_size = |keyword| { let keyword_to_gecko_size = |keyword| {
match keyword { match keyword {
SizeKeyword::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, ShapeExtent::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
SizeKeyword::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE, ShapeExtent::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE,
SizeKeyword::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER, ShapeExtent::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER,
SizeKeyword::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, ShapeExtent::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
SizeKeyword::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, ShapeExtent::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE,
SizeKeyword::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, ShapeExtent::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER,
} }
}; };
let (gecko_shape, gecko_size) = match shape { let (gecko_shape, gecko_size) = match shape {
GradientShape::Circle(ref length) => { EndingShape::Circle(ref circle) => {
let size = match *length { let size = match *circle {
LengthOrKeyword::Keyword(keyword) => { Circle::Extent(extent) => {
keyword_to_gecko_size(keyword) keyword_to_gecko_size(extent)
}, },
_ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE, _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
}; };
(NS_STYLE_GRADIENT_SHAPE_CIRCULAR as u8, size as u8) (NS_STYLE_GRADIENT_SHAPE_CIRCULAR as u8, size as u8)
}, },
GradientShape::Ellipse(ref length) => { EndingShape::Ellipse(ref ellipse) => {
let size = match *length { let size = match *ellipse {
LengthOrPercentageOrKeyword::Keyword(keyword) => { Ellipse::Extent(extent) => {
keyword_to_gecko_size(keyword) keyword_to_gecko_size(extent)
}, },
_ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE, _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE,
}; };
@ -291,22 +290,19 @@ impl nsStyleImage {
// Setting radius values depending shape // Setting radius values depending shape
match shape { match shape {
GradientShape::Circle(length) => { EndingShape::Circle(Circle::Radius(length)) => {
if let LengthOrKeyword::Length(len) = length { unsafe {
unsafe { (*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(length.0));
(*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(len.0)); (*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(length.0));
(*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(len.0));
}
} }
}, },
GradientShape::Ellipse(length) => { EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
if let LengthOrPercentageOrKeyword::LengthOrPercentage(first_len, second_len) = length { unsafe {
unsafe { (*gecko_gradient).mRadiusX.set(x);
(*gecko_gradient).mRadiusX.set(first_len); (*gecko_gradient).mRadiusY.set(y);
(*gecko_gradient).mRadiusY.set(second_len);
}
} }
}, },
_ => {},
} }
unsafe { unsafe {
(*gecko_gradient).mBgPosX.set(position.horizontal); (*gecko_gradient).mBgPosX.set(position.horizontal);

View file

@ -11,23 +11,49 @@ use cssparser::Color as CSSColor;
use std::f32::consts::PI; use std::f32::consts::PI;
use std::fmt; use std::fmt;
use style_traits::ToCss; 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::{Image as GenericImage, ImageRect as GenericImageRect};
use values::computed::{Angle, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue}; use values::computed::{Angle, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
use values::computed::position::Position; use values::computed::position::Position;
use values::specified; use values::generics::image::{CompatMode, ColorStop as GenericColorStop, EndingShape as GenericEndingShape};
use values::generics::image::{Gradient as GenericGradient, GradientItem as GenericGradientItem};
use values::generics::image::{Image as GenericImage, GradientKind as GenericGradientKind};
use values::generics::image::{ImageRect as GenericImageRect, LineDirection as GenericLineDirection};
use values::specified::image::LineDirection as SpecifiedLineDirection;
use values::specified::position::{X, Y}; use values::specified::position::{X, Y};
pub use values::specified::SizeKeyword;
/// Computed values for an image according to CSS-IMAGES. /// Computed values for an image according to CSS-IMAGES.
/// https://drafts.csswg.org/css-images/#image-values /// https://drafts.csswg.org/css-images/#image-values
pub type Image = GenericImage<Gradient, NumberOrPercentage>; pub type Image = GenericImage<Gradient, ImageRect>;
/// Computed values for a CSS gradient. /// Computed values for a CSS gradient.
/// https://drafts.csswg.org/css-images/#gradients /// https://drafts.csswg.org/css-images/#gradients
pub type Gradient = GenericGradient<GradientKind, CSSColor, LengthOrPercentage>; pub type Gradient = GenericGradient<
LineDirection,
Length,
LengthOrPercentage,
Position,
CSSColor,
>;
/// A computed gradient kind.
pub type GradientKind = GenericGradientKind<
LineDirection,
Length,
LengthOrPercentage,
Position,
>;
/// A computed gradient line direction.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum LineDirection {
/// An angle.
Angle(Angle),
/// A corner.
Corner(X, Y),
}
/// A computed radial gradient ending shape.
pub type EndingShape = GenericEndingShape<Length, LengthOrPercentage>;
/// A computed gradient item. /// A computed gradient item.
pub type GradientItem = GenericGradientItem<CSSColor, LengthOrPercentage>; pub type GradientItem = GenericGradientItem<CSSColor, LengthOrPercentage>;
@ -38,355 +64,65 @@ pub type ColorStop = GenericColorStop<CSSColor, LengthOrPercentage>;
/// Computed values for ImageRect. /// Computed values for ImageRect.
pub type ImageRect = GenericImageRect<NumberOrPercentage>; pub type ImageRect = GenericImageRect<NumberOrPercentage>;
impl ToCss for Gradient { impl GenericLineDirection for LineDirection {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn points_downwards(&self) -> bool {
if self.compat_mode == CompatMode::WebKit {
try!(dest.write_str("-webkit-"));
}
if self.repeating {
try!(dest.write_str("repeating-"));
}
match self.kind {
GradientKind::Linear(angle_or_corner) => {
try!(dest.write_str("linear-gradient("));
try!(angle_or_corner.to_css(dest, self.compat_mode));
},
GradientKind::Radial(ref shape, position) => {
try!(dest.write_str("radial-gradient("));
try!(shape.to_css(dest));
try!(dest.write_str(" at "));
try!(position.to_css(dest));
},
}
for item in &self.items {
try!(dest.write_str(", "));
try!(item.to_css(dest));
}
try!(dest.write_str(")"));
Ok(())
}
}
impl fmt::Debug for Gradient {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
GradientKind::Linear(angle_or_corner) => {
let _ = write!(f, "{:?}", angle_or_corner);
},
GradientKind::Radial(ref shape, position) => {
let _ = write!(f, "{:?} at {:?}", shape, position);
},
}
for item in &self.items {
let _ = write!(f, ", {:?}", item);
}
Ok(())
}
}
/// Computed values for CSS linear or radial gradients.
/// https://drafts.csswg.org/css-images/#gradients
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum GradientKind {
Linear(AngleOrCorner),
Radial(EndingShape, Position),
}
impl ToComputedValue for specified::GradientKind {
type ComputedValue = GradientKind;
#[inline]
fn to_computed_value(&self, context: &Context) -> GradientKind {
match *self { match *self {
specified::GradientKind::Linear(angle_or_corner) => { LineDirection::Angle(angle) => angle.radians() == PI,
GradientKind::Linear(angle_or_corner.to_computed_value(context)) LineDirection::Corner(..) => false,
},
specified::GradientKind::Radial(ref shape, ref position) => {
GradientKind::Radial(shape.to_computed_value(context),
position.to_computed_value(context))
},
} }
} }
#[inline]
fn from_computed_value(computed: &GradientKind) -> Self {
match *computed {
GradientKind::Linear(angle_or_corner) => {
specified::GradientKind::Linear(ToComputedValue::from_computed_value(&angle_or_corner))
},
GradientKind::Radial(ref shape, position) => {
specified::GradientKind::Radial(ToComputedValue::from_computed_value(shape),
ToComputedValue::from_computed_value(&position))
},
}
}
}
/// Computed values for EndingShape fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-ending-shape where W: fmt::Write
#[derive(Clone, PartialEq)] {
#[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 { match *self {
EndingShape::Circle(ref length) => { LineDirection::Angle(ref angle) => angle.to_css(dest),
try!(dest.write_str("circle ")); LineDirection::Corner(x, y) => {
try!(length.to_css(dest)); if compat_mode == CompatMode::Modern {
}, dest.write_str("to ")?;
EndingShape::Ellipse(ref length) => {
try!(dest.write_str("ellipse "));
try!(length.to_css(dest));
},
}
Ok(())
}
}
impl fmt::Debug for EndingShape {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
EndingShape::Circle(ref length) => {
let _ = write!(f, "circle {:?}", length);
},
EndingShape::Ellipse(ref length) => {
let _ = write!(f, "ellipse {:?}", length);
}
}
Ok(())
}
}
impl ToComputedValue for specified::GradientEndingShape {
type ComputedValue = EndingShape;
#[inline]
fn to_computed_value(&self, context: &Context) -> EndingShape {
match *self {
specified::GradientEndingShape::Circle(ref length) => {
EndingShape::Circle(length.to_computed_value(context))
},
specified::GradientEndingShape::Ellipse(ref length) => {
EndingShape::Ellipse(length.to_computed_value(context))
},
}
}
#[inline]
fn from_computed_value(computed: &EndingShape) -> Self {
match *computed {
EndingShape::Circle(ref length) => {
specified::GradientEndingShape::Circle(ToComputedValue::from_computed_value(length))
},
EndingShape::Ellipse(ref length) => {
specified::GradientEndingShape::Ellipse(ToComputedValue::from_computed_value(length))
},
}
}
}
/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum LengthOrKeyword {
Length(Length),
Keyword(SizeKeyword),
}
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),
}
}
}
impl fmt::Debug for LengthOrKeyword {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LengthOrKeyword::Length(ref length) => {
let _ = write!(f, "{:?}", length);
},
LengthOrKeyword::Keyword(keyword) => {
let _ = write!(f, "{:?}", keyword);
},
}
Ok(())
}
}
impl ToComputedValue for specified::LengthOrKeyword {
type ComputedValue = LengthOrKeyword;
#[inline]
fn to_computed_value(&self, context: &Context) -> LengthOrKeyword {
match *self {
specified::LengthOrKeyword::Length(ref length) => {
LengthOrKeyword::Length(length.to_computed_value(context))
},
specified::LengthOrKeyword::Keyword(keyword) => {
LengthOrKeyword::Keyword(keyword)
},
}
}
#[inline]
fn from_computed_value(computed: &LengthOrKeyword) -> Self {
match *computed {
LengthOrKeyword::Length(length) => {
specified::LengthOrKeyword::Length(ToComputedValue::from_computed_value(&length))
},
LengthOrKeyword::Keyword(keyword) => {
specified::LengthOrKeyword::Keyword(keyword)
},
}
}
}
/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-size
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum LengthOrPercentageOrKeyword {
LengthOrPercentage(LengthOrPercentage, LengthOrPercentage),
Keyword(SizeKeyword),
}
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, second_len) => {
try!(first_len.to_css(dest));
try!(dest.write_str(" "));
second_len.to_css(dest)
},
LengthOrPercentageOrKeyword::Keyword(keyword) => keyword.to_css(dest),
}
}
}
impl fmt::Debug for LengthOrPercentageOrKeyword {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, second_len) => {
let _ = write!(f, "{:?} {:?}", first_len, second_len);
},
LengthOrPercentageOrKeyword::Keyword(keyword) => {
let _ = write!(f, "{:?}", keyword);
},
}
Ok(())
}
}
impl ToComputedValue for specified::LengthOrPercentageOrKeyword {
type ComputedValue = LengthOrPercentageOrKeyword;
#[inline]
fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrKeyword {
match *self {
specified::LengthOrPercentageOrKeyword::LengthOrPercentage(ref first_len, ref second_len) => {
LengthOrPercentageOrKeyword::LengthOrPercentage(first_len.to_computed_value(context),
second_len.to_computed_value(context))
},
specified::LengthOrPercentageOrKeyword::Keyword(keyword) => {
LengthOrPercentageOrKeyword::Keyword(keyword)
},
}
}
#[inline]
fn from_computed_value(computed: &LengthOrPercentageOrKeyword) -> Self {
match *computed {
LengthOrPercentageOrKeyword::LengthOrPercentage(first_len, second_len) => {
specified::LengthOrPercentageOrKeyword::LengthOrPercentage(
ToComputedValue::from_computed_value(&first_len),
ToComputedValue::from_computed_value(&second_len))
},
LengthOrPercentageOrKeyword::Keyword(keyword) => {
specified::LengthOrPercentageOrKeyword::Keyword(keyword)
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum AngleOrCorner {
Angle(Angle),
Corner(X, Y)
}
impl ToComputedValue for specified::AngleOrCorner {
type ComputedValue = AngleOrCorner;
#[inline]
fn to_computed_value(&self, context: &Context) -> AngleOrCorner {
match *self {
specified::AngleOrCorner::None => {
AngleOrCorner::Angle(Angle::from_radians(PI))
},
specified::AngleOrCorner::Angle(angle) => {
AngleOrCorner::Angle(angle.to_computed_value(context))
},
specified::AngleOrCorner::Corner(horizontal, vertical) => {
match (horizontal, vertical) {
(None, Some(Y::Top)) => {
AngleOrCorner::Angle(Angle::from_radians(0.0))
},
(Some(X::Right), None) => {
AngleOrCorner::Angle(Angle::from_radians(PI * 0.5))
},
(None, Some(Y::Bottom)) => {
AngleOrCorner::Angle(Angle::from_radians(PI))
},
(Some(X::Left), None) => {
AngleOrCorner::Angle(Angle::from_radians(PI * 1.5))
},
(Some(horizontal), Some(vertical)) => {
AngleOrCorner::Corner(horizontal, vertical)
},
(None, None) => {
unreachable!()
}
} }
} x.to_css(dest)?;
} dest.write_str(" ")?;
} y.to_css(dest)
#[inline]
fn from_computed_value(computed: &AngleOrCorner) -> Self {
match *computed {
AngleOrCorner::Angle(ref angle) => {
specified::AngleOrCorner::Angle(specified::Angle::from_computed_value(angle))
}, },
AngleOrCorner::Corner(horizontal, vertical) => {
specified::AngleOrCorner::Corner(Some(horizontal), Some(vertical))
}
} }
} }
} }
impl AngleOrCorner { impl ToComputedValue for SpecifiedLineDirection {
fn to_css<W>(&self, dest: &mut W, mode: CompatMode) -> fmt::Result where W: fmt::Write { type ComputedValue = LineDirection;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self { match *self {
AngleOrCorner::Angle(angle) => angle.to_css(dest), SpecifiedLineDirection::Angle(ref angle) => {
AngleOrCorner::Corner(horizontal, vertical) => { LineDirection::Angle(angle.to_computed_value(context))
if mode == CompatMode::Modern { },
try!(dest.write_str("to ")); SpecifiedLineDirection::Horizontal(X::Left) => {
} LineDirection::Angle(Angle::Degree(270.))
try!(horizontal.to_css(dest)); },
try!(dest.write_str(" ")); SpecifiedLineDirection::Horizontal(X::Right) => {
try!(vertical.to_css(dest)); LineDirection::Angle(Angle::Degree(90.))
Ok(()) },
} SpecifiedLineDirection::Vertical(Y::Top) => {
LineDirection::Angle(Angle::Degree(0.))
},
SpecifiedLineDirection::Vertical(Y::Bottom) => {
LineDirection::Angle(Angle::Degree(180.))
},
SpecifiedLineDirection::Corner(x, y) => {
LineDirection::Corner(x, y)
},
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
match *computed {
LineDirection::Angle(ref angle) => {
SpecifiedLineDirection::Angle(ToComputedValue::from_computed_value(angle))
},
LineDirection::Corner(x, y) => {
SpecifiedLineDirection::Corner(x, y)
},
} }
} }
} }

View file

@ -12,8 +12,7 @@ use super::{Number, ToComputedValue, Context};
use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified}; use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified};
use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength, ViewportPercentageLength}; use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength, ViewportPercentageLength};
pub use super::image::{EndingShape as GradientShape, Gradient, GradientKind, Image}; pub use super::image::Image;
pub use super::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone}; pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone};
impl ToComputedValue for specified::NoCalcLength { impl ToComputedValue for specified::NoCalcLength {

View file

@ -22,8 +22,7 @@ use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as G
pub use app_units::Au; pub use app_units::Au;
pub use cssparser::Color as CSSColor; pub use cssparser::Color as CSSColor;
pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientItem, LayerImage}; pub use self::image::{Gradient, GradientItem, LayerImage, LineDirection, Image, ImageRect};
pub use self::image::{GradientKind, Image, ImageRect, LengthOrKeyword, LengthOrPercentageOrKeyword};
pub use super::{Auto, Either, None_}; pub use super::{Auto, Either, None_};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};

View file

@ -19,13 +19,13 @@ use values::specified::url::SpecifiedUrl;
/// [image]: https://drafts.csswg.org/css-images/#image-values /// [image]: https://drafts.csswg.org/css-images/#image-values
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Image<G, N> { pub enum Image<Gradient, ImageRect> {
/// A `<url()>` image. /// A `<url()>` image.
Url(SpecifiedUrl), Url(SpecifiedUrl),
/// A `<gradient>` image. /// A `<gradient>` image.
Gradient(G), Gradient(Gradient),
/// A `-moz-image-rect` image /// A `-moz-image-rect` image
Rect(ImageRect<N>), Rect(ImageRect),
/// A `-moz-element(# <element-id>)` /// A `-moz-element(# <element-id>)`
Element(Atom), Element(Atom),
} }
@ -34,11 +34,11 @@ pub enum Image<G, N> {
/// https://drafts.csswg.org/css-images/#gradients /// https://drafts.csswg.org/css-images/#gradients
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Gradient<K, C, L> { pub struct Gradient<LineDirection, Length, LengthOrPercentage, Position, Color> {
/// Gradients can be linear or radial. /// Gradients can be linear or radial.
pub kind: K, pub kind: GradientKind<LineDirection, Length, LengthOrPercentage, Position>,
/// The color stops and interpolation hints. /// The color stops and interpolation hints.
pub items: Vec<GradientItem<C, L>>, pub items: Vec<GradientItem<Color, LengthOrPercentage>>,
/// True if this is a repeating gradient. /// True if this is a repeating gradient.
pub repeating: bool, pub repeating: bool,
/// Compatibility mode. /// Compatibility mode.
@ -55,26 +55,76 @@ pub enum CompatMode {
WebKit, WebKit,
} }
/// A gradient kind.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum GradientKind<LineDirection, Length, LengthOrPercentage, Position> {
/// A linear gradient.
Linear(LineDirection),
/// A radial gradient.
Radial(EndingShape<Length, LengthOrPercentage>, Position),
}
/// A radial gradient's ending shape.
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum EndingShape<Length, LengthOrPercentage> {
/// A circular gradient.
Circle(Circle<Length>),
/// An elliptic gradient.
Ellipse(Ellipse<LengthOrPercentage>),
}
/// A circle shape.
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Circle<Length> {
/// A circle radius.
Radius(Length),
/// A circle extent.
Extent(ShapeExtent),
}
/// An ellipse shape.
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum Ellipse<LengthOrPercentage> {
/// An ellipse pair of radii.
Radii(LengthOrPercentage, LengthOrPercentage),
/// An ellipse extent.
Extent(ShapeExtent),
}
/// https://drafts.csswg.org/css-images/#typedef-extent-keyword
define_css_keyword_enum!(ShapeExtent:
"closest-side" => ClosestSide,
"farthest-side" => FarthestSide,
"closest-corner" => ClosestCorner,
"farthest-corner" => FarthestCorner,
"contain" => Contain,
"cover" => Cover
);
/// A gradient item. /// A gradient item.
/// https://drafts.csswg.org/css-images-4/#color-stop-syntax /// https://drafts.csswg.org/css-images-4/#color-stop-syntax
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum GradientItem<C, L> { pub enum GradientItem<Color, LengthOrPercentage> {
/// A color stop. /// A color stop.
ColorStop(ColorStop<C, L>), ColorStop(ColorStop<Color, LengthOrPercentage>),
/// An interpolation hint. /// An interpolation hint.
InterpolationHint(L), InterpolationHint(LengthOrPercentage),
} }
/// A color stop. /// A color stop.
/// https://drafts.csswg.org/css-images/#typedef-color-stop-list /// https://drafts.csswg.org/css-images/#typedef-color-stop-list
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct ColorStop<C, L> { pub struct ColorStop<Color, LengthOrPercentage> {
/// The color of this stop. /// The color of this stop.
pub color: C, pub color: Color,
/// The position of this stop. /// The position of this stop.
pub position: Option<L>, pub position: Option<LengthOrPercentage>,
} }
/// Values for `moz-image-rect`. /// Values for `moz-image-rect`.
@ -83,16 +133,16 @@ pub struct ColorStop<C, L> {
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct ImageRect<C> { pub struct ImageRect<NumberOrPercentage> {
pub url: SpecifiedUrl, pub url: SpecifiedUrl,
pub top: C, pub top: NumberOrPercentage,
pub bottom: C, pub bottom: NumberOrPercentage,
pub right: C, pub right: NumberOrPercentage,
pub left: C, pub left: NumberOrPercentage,
} }
impl<G, N> fmt::Debug for Image<G, N> impl<G, R> fmt::Debug for Image<G, R>
where G: fmt::Debug, N: fmt::Debug, where G: fmt::Debug, R: fmt::Debug,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
@ -108,8 +158,8 @@ impl<G, N> fmt::Debug for Image<G, N>
} }
} }
impl<G, N> ToCss for Image<G, N> impl<G, R> ToCss for Image<G, R>
where G: ToCss, N: ToCss, where G: ToCss, R: ToCss,
{ {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self { match *self {
@ -125,7 +175,7 @@ impl<G, N> ToCss for Image<G, N>
} }
} }
impl<G, N> HasViewportPercentage for Image<G, N> impl<G, R> HasViewportPercentage for Image<G, R>
where G: HasViewportPercentage where G: HasViewportPercentage
{ {
fn has_viewport_percentage(&self) -> bool { fn has_viewport_percentage(&self) -> bool {
@ -136,11 +186,11 @@ impl<G, N> HasViewportPercentage for Image<G, N>
} }
} }
impl<G, N> ToComputedValue for Image<G, N> impl<G, R> ToComputedValue for Image<G, R>
where G: ToComputedValue, N: ToComputedValue, where G: ToComputedValue, R: ToComputedValue,
{ {
type ComputedValue = Image<<G as ToComputedValue>::ComputedValue, type ComputedValue = Image<<G as ToComputedValue>::ComputedValue,
<N as ToComputedValue>::ComputedValue>; <R as ToComputedValue>::ComputedValue>;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self { match *self {
@ -177,8 +227,64 @@ impl<G, N> ToComputedValue for Image<G, N>
} }
} }
impl<K, C, L> HasViewportPercentage for Gradient<K, C, L> impl<D, L, LoP, P, C> ToCss for Gradient<D, L, LoP, P, C>
where K: HasViewportPercentage, L: HasViewportPercentage, where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss,
{
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if self.compat_mode == CompatMode::WebKit {
dest.write_str("-webkit-")?;
}
if self.repeating {
dest.write_str("repeating-")?;
}
dest.write_str(self.kind.label())?;
dest.write_str("-gradient(")?;
let mut skip_comma = match self.kind {
GradientKind::Linear(ref direction) if direction.points_downwards() => true,
GradientKind::Linear(ref direction) => {
direction.to_css(dest, self.compat_mode)?;
false
},
GradientKind::Radial(ref shape, ref position) => {
let omit_shape = match *shape {
EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) |
EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => {
true
},
_ => false,
};
if self.compat_mode == CompatMode::Modern {
if !omit_shape {
shape.to_css(dest)?;
dest.write_str(" ")?;
}
dest.write_str("at ")?;
position.to_css(dest)?;
} else {
position.to_css(dest)?;
if !omit_shape {
dest.write_str(", ")?;
shape.to_css(dest)?;
}
}
false
},
};
for item in &self.items {
if !skip_comma {
dest.write_str(", ")?;
}
skip_comma = false;
item.to_css(dest)?;
}
dest.write_str(")")
}
}
impl<D, L, LoP, P, C> HasViewportPercentage for Gradient<D, L, LoP, P, C>
where L: HasViewportPercentage,
LoP: HasViewportPercentage,
P: HasViewportPercentage,
{ {
fn has_viewport_percentage(&self) -> bool { fn has_viewport_percentage(&self) -> bool {
self.kind.has_viewport_percentage() || self.kind.has_viewport_percentage() ||
@ -186,12 +292,18 @@ impl<K, C, L> HasViewportPercentage for Gradient<K, C, L>
} }
} }
impl<K, C, L> ToComputedValue for Gradient<K, C, L> impl<D, L, LoP, P, C> ToComputedValue for Gradient<D, L, LoP, P, C>
where K: ToComputedValue, C: ToComputedValue, L: ToComputedValue, where D: ToComputedValue,
L: ToComputedValue,
LoP: ToComputedValue,
P: ToComputedValue,
C: ToComputedValue,
{ {
type ComputedValue = Gradient<<K as ToComputedValue>::ComputedValue, type ComputedValue = Gradient<<D as ToComputedValue>::ComputedValue,
<C as ToComputedValue>::ComputedValue, <L as ToComputedValue>::ComputedValue,
<L as ToComputedValue>::ComputedValue>; <LoP as ToComputedValue>::ComputedValue,
<P as ToComputedValue>::ComputedValue,
<C as ToComputedValue>::ComputedValue>;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
Gradient { Gradient {
@ -212,6 +324,168 @@ impl<K, C, L> ToComputedValue for Gradient<K, C, L>
} }
} }
impl<D, L, LoP, P> GradientKind<D, L, LoP, P> {
fn label(&self) -> &str {
match *self {
GradientKind::Linear(..) => "linear",
GradientKind::Radial(..) => "radial",
}
}
}
impl<D, L, LoP, P> HasViewportPercentage for GradientKind<D, L, LoP, P>
where L: HasViewportPercentage,
LoP: HasViewportPercentage,
P: HasViewportPercentage
{
fn has_viewport_percentage(&self) -> bool {
match *self {
GradientKind::Linear(_) => false,
GradientKind::Radial(ref shape, ref position) => {
shape.has_viewport_percentage() || position.has_viewport_percentage()
},
}
}
}
impl<D, L, LoP, P> ToComputedValue for GradientKind<D, L, LoP, P>
where D: ToComputedValue,
L: ToComputedValue,
LoP: ToComputedValue,
P: ToComputedValue,
{
type ComputedValue = GradientKind<<D as ToComputedValue>::ComputedValue,
<L as ToComputedValue>::ComputedValue,
<LoP as ToComputedValue>::ComputedValue,
<P as ToComputedValue>::ComputedValue>;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
GradientKind::Linear(ref direction) => {
GradientKind::Linear(direction.to_computed_value(context))
},
GradientKind::Radial(ref shape, ref position) => {
GradientKind::Radial(shape.to_computed_value(context), position.to_computed_value(context))
},
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
match *computed {
GradientKind::Linear(ref direction) => {
GradientKind::Linear(ToComputedValue::from_computed_value(direction))
},
GradientKind::Radial(ref shape, ref position) => {
GradientKind::Radial(
ToComputedValue::from_computed_value(shape),
ToComputedValue::from_computed_value(position),
)
}
}
}
}
/// The direction of a linear gradient.
pub trait LineDirection {
/// Whether this direction points towards, and thus can be omitted.
fn points_downwards(&self) -> bool;
/// Serialises this direction according to the compatibility mode.
fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
where W: fmt::Write;
}
impl<L, LoP> ToCss for EndingShape<L, LoP>
where L: ToCss, LoP: ToCss,
{
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
EndingShape::Circle(Circle::Extent(ShapeExtent::FarthestCorner)) |
EndingShape::Circle(Circle::Extent(ShapeExtent::Cover)) => {
dest.write_str("circle")
},
EndingShape::Circle(Circle::Extent(keyword)) => {
dest.write_str("circle ")?;
keyword.to_css(dest)
},
EndingShape::Circle(Circle::Radius(ref length)) => {
length.to_css(dest)
},
EndingShape::Ellipse(Ellipse::Extent(keyword)) => {
keyword.to_css(dest)
},
EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
x.to_css(dest)?;
dest.write_str(" ")?;
y.to_css(dest)
},
}
}
}
impl<L, LoP> HasViewportPercentage for EndingShape<L, LoP>
where L: HasViewportPercentage, LoP: HasViewportPercentage,
{
fn has_viewport_percentage(&self) -> bool {
match *self {
EndingShape::Circle(Circle::Radius(ref length)) => {
length.has_viewport_percentage()
},
EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
x.has_viewport_percentage() || y.has_viewport_percentage()
},
_ => false,
}
}
}
impl<L, LoP> ToComputedValue for EndingShape<L, LoP>
where L: ToComputedValue, LoP: ToComputedValue,
{
type ComputedValue = EndingShape<<L as ToComputedValue>::ComputedValue,
<LoP as ToComputedValue>::ComputedValue>;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
EndingShape::Circle(Circle::Radius(ref length)) => {
EndingShape::Circle(Circle::Radius(length.to_computed_value(context)))
},
EndingShape::Circle(Circle::Extent(extent)) => {
EndingShape::Circle(Circle::Extent(extent))
},
EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
EndingShape::Ellipse(Ellipse::Radii(
x.to_computed_value(context),
y.to_computed_value(context),
))
},
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
EndingShape::Ellipse(Ellipse::Extent(extent))
},
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
match *computed {
EndingShape::Circle(Circle::Radius(ref length)) => {
EndingShape::Circle(Circle::Radius(ToComputedValue::from_computed_value(length)))
},
EndingShape::Circle(Circle::Extent(extent)) => {
EndingShape::Circle(Circle::Extent(extent))
},
EndingShape::Ellipse(Ellipse::Radii(ref x, ref y)) => {
EndingShape::Ellipse(Ellipse::Radii(
ToComputedValue::from_computed_value(x),
ToComputedValue::from_computed_value(y),
))
},
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
EndingShape::Ellipse(Ellipse::Extent(extent))
},
}
}
}
impl<C, L> ToCss for GradientItem<C, L> impl<C, L> ToCss for GradientItem<C, L>
where C: ToCss, L: ToCss, where C: ToCss, L: ToCss,
{ {
@ -342,7 +616,7 @@ impl<C> ToComputedValue for ImageRect<C>
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
ImageRect { ImageRect {
url: self.url.clone(), url: self.url.to_computed_value(context),
top: self.top.to_computed_value(context), top: self.top.to_computed_value(context),
right: self.right.to_computed_value(context), right: self.right.to_computed_value(context),
bottom: self.bottom.to_computed_value(context), bottom: self.bottom.to_computed_value(context),
@ -352,7 +626,7 @@ impl<C> ToComputedValue for ImageRect<C>
fn from_computed_value(computed: &Self::ComputedValue) -> Self { fn from_computed_value(computed: &Self::ComputedValue) -> Self {
ImageRect { ImageRect {
url: computed.url.clone(), url: ToComputedValue::from_computed_value(&computed.url),
top: ToComputedValue::from_computed_value(&computed.top), top: ToComputedValue::from_computed_value(&computed.top),
right: ToComputedValue::from_computed_value(&computed.right), right: ToComputedValue::from_computed_value(&computed.right),
bottom: ToComputedValue::from_computed_value(&computed.bottom), bottom: ToComputedValue::from_computed_value(&computed.bottom),

View file

@ -12,22 +12,56 @@ 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::f32::consts::PI;
use std::fmt; use std::fmt;
use style_traits::ToCss; use style_traits::ToCss;
use values::generics::image::{CompatMode, ColorStop as GenericColorStop}; use values::generics::image::{Circle, CompatMode, Ellipse, ColorStop as GenericColorStop};
use values::generics::image::{Gradient as GenericGradient, GradientItem as GenericGradientItem}; 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::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::position::{Position, X, Y};
use values::specified::url::SpecifiedUrl; use values::specified::url::SpecifiedUrl;
/// Specified values for an image according to CSS-IMAGES. /// Specified values for an image according to CSS-IMAGES.
/// https://drafts.csswg.org/css-images/#image-values /// 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. /// Specified values for a CSS gradient.
/// https://drafts.csswg.org/css-images/#gradients /// 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. /// A specified gradient item.
pub type GradientItem = GenericGradientItem<CSSColor, LengthOrPercentage>; 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 { impl Gradient {
/// Parses a gradient from the given arguments. /// Parses a gradient from the given arguments.
pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Gradient, ()> { pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Gradient, ()> {
fn parse<F>(context: &ParserContext, input: &mut Parser, parse_kind: F) enum Shape {
-> Result<(GradientKind, Vec<GradientItem>), ()> Linear,
where F: FnOnce(&ParserContext, &mut Parser) -> Result<GradientKind, ()> Radial,
{ }
input.parse_nested_block(|input| {
let kind = try!(parse_kind(context, input)); let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &try!(input.expect_function()),
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()),
"linear-gradient" => { "linear-gradient" => {
try!(parse(context, input, GradientKind::parse_modern_linear)) (Shape::Linear, false, CompatMode::Modern)
}, },
"-webkit-linear-gradient" => { "-webkit-linear-gradient" => {
compat_mode = CompatMode::WebKit; (Shape::Linear, false, CompatMode::WebKit)
try!(parse(context, input, GradientKind::parse_webkit_linear))
}, },
"repeating-linear-gradient" => { "repeating-linear-gradient" => {
repeating = true; (Shape::Linear, true, CompatMode::Modern)
try!(parse(context, input, GradientKind::parse_modern_linear))
}, },
"-webkit-repeating-linear-gradient" => { "-webkit-repeating-linear-gradient" => {
repeating = true; (Shape::Linear, true, CompatMode::WebKit)
compat_mode = CompatMode::WebKit;
try!(parse(context, input, GradientKind::parse_webkit_linear))
}, },
"radial-gradient" => { "radial-gradient" => {
try!(parse(context, input, GradientKind::parse_modern_radial)) (Shape::Radial, false, CompatMode::Modern)
}, },
"-webkit-radial-gradient" => { "-webkit-radial-gradient" => {
compat_mode = CompatMode::WebKit; (Shape::Radial, false, CompatMode::WebKit)
try!(parse(context, input, GradientKind::parse_webkit_radial))
}, },
"repeating-radial-gradient" => { "repeating-radial-gradient" => {
repeating = true; (Shape::Radial, true, CompatMode::Modern)
try!(parse(context, input, GradientKind::parse_modern_radial))
}, },
"-webkit-repeating-radial-gradient" => { "-webkit-repeating-radial-gradient" => {
repeating = true; (Shape::Radial, true, CompatMode::WebKit)
compat_mode = CompatMode::WebKit;
try!(parse(context, input, GradientKind::parse_webkit_radial))
}, },
_ => { return Err(()); } _ => { 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 { Ok(Gradient {
items: items, items: items,
repeating: repeating, repeating: repeating,
kind: gradient_kind, kind: kind,
compat_mode: compat_mode, compat_mode: compat_mode,
}) })
} }
@ -197,167 +187,205 @@ impl Gradient {
} }
} }
/// Specified values for CSS linear or radial gradients. impl GradientKind {
/// https://drafts.csswg.org/css-images/#gradients fn parse_linear(context: &ParserContext,
#[derive(Clone, PartialEq, Debug)] input: &mut Parser,
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] compat_mode: CompatMode)
pub enum GradientKind { -> Result<Self, ()> {
/// A `<linear-gradient()>`: let direction = if let Ok(d) = input.try(|i| LineDirection::parse(context, i, compat_mode)) {
/// input.expect_comma()?;
/// https://drafts.csswg.org/css-images/#funcdef-linear-gradient d
Linear(AngleOrCorner), } else {
LineDirection::Vertical(Y::Bottom)
};
Ok(GenericGradientKind::Linear(direction))
}
/// A `<radial-gradient()>`: fn parse_radial(context: &ParserContext,
/// input: &mut Parser,
/// https://drafts.csswg.org/css-images/#radial-gradients compat_mode: CompatMode)
Radial(EndingShape, Position), -> 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 { impl GenericsLineDirection for LineDirection {
/// Parses a linear gradient kind from the given arguments. fn points_downwards(&self) -> bool {
fn parse_modern_linear(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> { match *self {
let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { LineDirection::Angle(ref angle) => angle.radians() == PI,
try!(input.expect_comma()); LineDirection::Vertical(Y::Bottom) => true,
AngleOrCorner::Angle(angle) _ => false,
} 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))
} }
fn parse_webkit_linear(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> { fn to_css<W>(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result
let direction = if let Ok(angle) = input.try(|i| Angle::parse_with_unitless(context, i)) { where W: fmt::Write
AngleOrCorner::Angle(angle) {
} else { match *self {
if let Ok(value) = input.try(X::parse) { LineDirection::Angle(angle) => {
AngleOrCorner::Corner(Some(value), input.try(Y::parse).ok()) angle.to_css(dest)
} else { },
if let Ok(value) = input.try(Y::parse) { LineDirection::Horizontal(x) => {
AngleOrCorner::Corner(input.try(X::parse).ok(), Some(value)) if compat_mode == CompatMode::Modern {
} else { dest.write_str("to ")?;
AngleOrCorner::None }
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)));
} }
} }
}; return Ok(GenericEndingShape::Circle(Circle::Extent(ShapeExtent::FarthestCorner)));
if direction != AngleOrCorner::None {
try!(input.expect_comma());
} }
Ok(GradientKind::Linear(direction)) 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)));
/// Parses a modern radial gradient from the given arguments. }
pub fn parse_modern_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> { if compat_mode == CompatMode::Modern {
let mut needs_comma = true; let pair: Result<_, ()> = input.try(|i| {
let x = LengthOrPercentage::parse(context, i)?;
// Ending shape and position can be in various order. Checks all probabilities. let y = LengthOrPercentage::parse(context, i)?;
let (shape, position) = if let Ok(position) = input.try(|i| parse_position(context, i)) { Ok((x, y))
// Handle just <position> });
(EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)), position) if let Ok((x, y)) = pair {
} else if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) { return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x, y)));
// Handle <LengthOrPercentage> <LengthOrPercentage> <shape>? <position>? }
let _ = input.try(|input| input.expect_ident_matching("ellipse")); }
(EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(first, second)), return Ok(GenericEndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)));
input.try(|i| parse_position(context, i)).unwrap_or(Position::center())) }
} else if let Ok(length) = input.try(|i| Length::parse(context, i)) { if let Ok(length) = input.try(|i| Length::parse(context, i)) {
// Handle <Length> <circle>? <position>? if let Ok(y) = input.try(|i| LengthOrPercentage::parse(context, i)) {
let _ = input.try(|input| input.expect_ident_matching("circle")); if compat_mode == CompatMode::Modern {
(EndingShape::Circle(LengthOrKeyword::Length(length)), let _ = input.try(|i| i.expect_ident_matching("ellipse"));
input.try(|i| parse_position(context, i)).unwrap_or(Position::center())) }
} else if let Ok(keyword) = input.try(SizeKeyword::parse_modern) { return Ok(GenericEndingShape::Ellipse(Ellipse::Radii(length.into(), y)));
// Handle <keyword> <shape-keyword>? <position>? }
let shape = if input.try(|input| input.expect_ident_matching("circle")).is_ok() { if compat_mode == CompatMode::Modern {
EndingShape::Circle(LengthOrKeyword::Keyword(keyword)) 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 { } else {
let _ = input.try(|input| input.expect_ident_matching("ellipse")); if compat_mode == CompatMode::Modern {
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(keyword)) i.expect_ident_matching("ellipse")?;
}
LengthOrPercentage::parse(context, i)?
}; };
(shape, input.try(|i| parse_position(context, i)).unwrap_or(Position::center())) Ok(GenericEndingShape::Ellipse(Ellipse::Radii(x.into(), y)))
} 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))
} }
}
/// Parses a webkit radial gradient from the given arguments. impl ShapeExtent {
/// https://compat.spec.whatwg.org/#css-gradients-webkit-radial-gradient fn parse_with_compat_mode(input: &mut Parser,
pub fn parse_webkit_radial(context: &ParserContext, input: &mut Parser) -> Result<GradientKind, ()> { compat_mode: CompatMode)
let position = if let Ok(position) = input.try(|i| Position::parse(context, i)) { -> Result<Self, ()> {
try!(input.expect_comma()); match try!(Self::parse(input)) {
position ShapeExtent::Contain | ShapeExtent::Cover if compat_mode == CompatMode::Modern => Err(()),
} else { keyword => Ok(keyword),
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());
} }
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 { impl Parse for ColorStop {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> { fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
Ok(ColorStop { 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>. /// Specified values for none | <image> | <mask-source>.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]

View file

@ -24,8 +24,8 @@ use values::computed::{ComputedValueAsSpecified, Context};
use values::specified::calc::CalcNode; use values::specified::calc::CalcNode;
pub use values::specified::calc::CalcLengthOrPercentage; pub use values::specified::calc::CalcLengthOrPercentage;
pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient}; pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
pub use super::image::{GradientKind, Image, LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword}; pub use super::image::{GradientKind, Image};
/// Number of app units per pixel /// Number of app units per pixel
pub const AU_PER_PX: CSSFloat = 60.; pub const AU_PER_PX: CSSFloat = 60.;

View file

@ -28,9 +28,8 @@ use values::specified::calc::CalcNode;
pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
pub use self::color::Color; pub use self::color::Color;
pub use self::grid::{GridLine, TrackKeyword}; pub use self::grid::{GridLine, TrackKeyword};
pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient}; pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
pub use self::image::{GradientItem, GradientKind, Image, ImageRect, LayerImage}; pub use self::image::{GradientItem, GradientKind, Image, ImageRect, LayerImage};
pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword};
pub use self::length::AbsoluteLength; pub use self::length::AbsoluteLength;
pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage}; pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto}; pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};

View file

@ -2,16 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use euclid::size::TypedSize2D;
use parsing::parse; use parsing::parse;
use std::f32::consts::PI;
use style::context::QuirksMode;
use style::font_metrics::ServoMetricsProvider;
use style::media_queries::{Device, MediaType};
use style::properties::{ComputedValues, StyleBuilder};
use style::values::computed;
use style::values::computed::{Angle, Context, ToComputedValue};
use style::values::specified;
use style::values::specified::image::*; use style::values::specified::image::*;
use style_traits::ToCss; use style_traits::ToCss;
@ -37,101 +28,77 @@ fn test_linear_gradient() {
// Parsing without <angle> and <side-or-corner> // Parsing without <angle> and <side-or-corner>
assert_roundtrip_with_context!(Image::parse, "linear-gradient(red, green)"); assert_roundtrip_with_context!(Image::parse, "linear-gradient(red, green)");
// AngleOrCorner::None should become AngleOrCorner::Angle(Angle(PI)) when parsed
// Note that Angle(PI) is correct for top-to-bottom rendering, whereas Angle(0) would render bottom-to-top.
// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/angle
let viewport_size = TypedSize2D::new(0., 0.);
let initial_style = ComputedValues::initial_values();
let device = Device::new(MediaType::Screen, viewport_size);
let specified_context = Context {
is_root_element: true,
device: &device,
inherited_style: initial_style,
layout_parent_style: initial_style,
style: StyleBuilder::for_derived_style(&initial_style),
cached_system_font: None,
font_metrics_provider: &ServoMetricsProvider,
in_media_query: false,
quirks_mode: QuirksMode::NoQuirks,
};
assert_eq!(specified::AngleOrCorner::None.to_computed_value(&specified_context),
computed::AngleOrCorner::Angle(Angle::from_radians(PI)));
} }
#[test] #[test]
fn test_radial_gradient() { fn test_radial_gradient() {
// Parsing with all values // Parsing with all values
assert_roundtrip_with_context!(Image::parse, "radial-gradient(circle closest-side at 20px 30px, red, green)"); assert_roundtrip_with_context!(Image::parse, "radial-gradient(circle closest-side at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, "radial-gradient(ellipse closest-side at 20px 30px, red, green)"); assert_roundtrip_with_context!(Image::parse, "radial-gradient(ellipse closest-side at 20px 30px, red, green)",
"radial-gradient(closest-side at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side circle at 20px 30px, red, green)", assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side circle at 20px 30px, red, green)",
"radial-gradient(circle closest-side at 20px 30px, red, green)"); "radial-gradient(circle closest-side at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side ellipse at 20px 30px, red, green)", assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side ellipse at 20px 30px, red, green)",
"radial-gradient(ellipse closest-side at 20px 30px, red, green)"); "radial-gradient(closest-side at 20px 30px, red, green)");
// Parsing with <shape-keyword> and <size> reversed // Parsing with <shape-keyword> and <size> reversed
assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side circle at 20px 30px, red, green)", assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-side circle at 20px 30px, red, green)",
"radial-gradient(circle closest-side at 20px 30px, red, green)"); "radial-gradient(circle closest-side at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-corner ellipse at 20px 30px, red, green)", assert_roundtrip_with_context!(Image::parse, "radial-gradient(closest-corner ellipse at 20px 30px, red, green)",
"radial-gradient(ellipse closest-corner at 20px 30px, red, green)"); "radial-gradient(closest-corner at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, "radial-gradient(30px circle, red, green)", assert_roundtrip_with_context!(Image::parse, "radial-gradient(30px circle, red, green)",
"radial-gradient(circle 30px at center center, red, green)"); "radial-gradient(30px at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, "radial-gradient(30px 40px ellipse, red, green)", assert_roundtrip_with_context!(Image::parse, "radial-gradient(30px 40px ellipse, red, green)",
"radial-gradient(ellipse 30px 40px at center center, red, green)"); "radial-gradient(30px 40px at center center, red, green)");
// Parsing without <size> // Parsing without <size>
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(circle, red, green)", "radial-gradient(circle, red, green)",
"radial-gradient(circle farthest-corner at center center, red, green)"); "radial-gradient(circle at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(ellipse, red, green)", "radial-gradient(ellipse, red, green)",
"radial-gradient(ellipse farthest-corner at center center, red, green)"); "radial-gradient(at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(circle at 20px 30px, red, green)", "radial-gradient(circle at 20px 30px, red, green)");
"radial-gradient(circle farthest-corner at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(ellipse at 20px 30px, red, green)", "radial-gradient(ellipse at 20px 30px, red, green)",
"radial-gradient(ellipse farthest-corner at 20px 30px, red, green)"); "radial-gradient(at 20px 30px, red, green)");
// Parsing without <shape-keyword> // Parsing without <shape-keyword>
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(20px at 20px 30px, red, green)", "radial-gradient(20px at 20px 30px, red, green)");
"radial-gradient(circle 20px at 20px 30px, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(20px 30px at left center, red, green)", "radial-gradient(20px 30px at left center, red, green)");
"radial-gradient(ellipse 20px 30px at left center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(closest-side at center, red, green)", "radial-gradient(closest-side at center, red, green)",
"radial-gradient(ellipse closest-side at center center, red, green)"); "radial-gradient(closest-side at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(20px, red, green)", "radial-gradient(20px, red, green)",
"radial-gradient(circle 20px at center center, red, green)"); "radial-gradient(20px at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(20px 30px, red, green)", "radial-gradient(20px 30px, red, green)",
"radial-gradient(ellipse 20px 30px at center center, red, green)"); "radial-gradient(20px 30px at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(closest-side, red, green)", "radial-gradient(closest-side, red, green)",
"radial-gradient(ellipse closest-side at center center, red, green)"); "radial-gradient(closest-side at center center, red, green)");
// Parsing without <shape-keyword> and <size> // Parsing without <shape-keyword> and <size>
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(at center, red, green)", "radial-gradient(at center, red, green)",
"radial-gradient(ellipse farthest-corner at center center, red, green)"); "radial-gradient(at center center, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(at center bottom, red, green)", "radial-gradient(at center bottom, red, green)");
"radial-gradient(ellipse farthest-corner at center bottom, red, green)");
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(at 40px 50px, red, green)", "radial-gradient(at 40px 50px, red, green)");
"radial-gradient(ellipse farthest-corner at 40px 50px, red, green)");
// Parsing with just color stops // Parsing with just color stops
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"radial-gradient(red, green)", "radial-gradient(red, green)",
"radial-gradient(ellipse farthest-corner at center center, red, green)"); "radial-gradient(at center center, red, green)");
// Parsing repeating radial gradient // Parsing repeating radial gradient
assert_roundtrip_with_context!(Image::parse, assert_roundtrip_with_context!(Image::parse,
"repeating-radial-gradient(red, green)", "repeating-radial-gradient(red, green)",
"repeating-radial-gradient(ellipse farthest-corner at center center, red, green)"); "repeating-radial-gradient(at center center, red, green)");
} }