From abef5da9d88a66df3856e9fb38e19932c277ed84 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Fri, 12 May 2017 17:28:05 +0200 Subject: [PATCH] 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. --- components/layout/display_list_builder.rs | 77 ++- components/style/gecko/conversions.rs | 62 +- components/style/values/computed/image.rs | 426 +++---------- components/style/values/computed/length.rs | 3 +- components/style/values/computed/mod.rs | 3 +- components/style/values/generics/image.rs | 342 +++++++++-- components/style/values/specified/image.rs | 635 ++++++++------------ components/style/values/specified/length.rs | 4 +- components/style/values/specified/mod.rs | 3 +- tests/unit/style/parsing/image.rs | 75 +-- 10 files changed, 742 insertions(+), 888 deletions(-) diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 14bffb0a1f6..5f33702d693 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -58,11 +58,12 @@ use style::properties::longhands::border_image_repeat::computed_value::RepeatKey use style::properties::style_structs; use style::servo::restyle_damage::REPAINT; use style::values::{Either, RGBA}; -use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind, LengthOrPercentage}; -use style::values::computed::{LengthOrPercentageOrAuto, LengthOrKeyword, LengthOrPercentageOrKeyword}; -use style::values::computed::{NumberOrPercentage, Position}; -use style::values::computed::image::{EndingShape, SizeKeyword}; -use style::values::generics::image::{GradientItem as GenericGradientItem, Image}; +use style::values::computed::{Gradient, GradientItem, LengthOrPercentage}; +use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position}; +use style::values::computed::image::{EndingShape, LineDirection}; +use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape}; +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_traits::CSSPixel; use style_traits::cursor::Cursor; @@ -391,7 +392,7 @@ pub trait FragmentDisplayListBuilding { fn convert_linear_gradient(&self, bounds: &Rect, stops: &[GradientItem], - angle_or_corner: &AngleOrCorner, + direction: &LineDirection, repeating: bool, style: &ServoComputedValues) -> display_list::Gradient; @@ -759,36 +760,46 @@ fn get_ellipse_radius(size: &Size2D, center: &Point2D, cmp: F) -> Siz /// Determines the radius of a circle if it was not explictly provided. /// 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, center: &Point2D) -> Size2D { - use style::values::computed::image::SizeKeyword::*; let radius = match keyword { - ClosestSide | Contain => { + ShapeExtent::ClosestSide | ShapeExtent::Contain => { let dist = get_distance_to_sides(size, center, ::std::cmp::min); ::std::cmp::min(dist.width, dist.height) } - FarthestSide => { + ShapeExtent::FarthestSide => { let dist = get_distance_to_sides(size, center, ::std::cmp::max); ::std::cmp::max(dist.width, dist.height) } - ClosestCorner => get_distance_to_corner(size, center, ::std::cmp::min), - FarthestCorner | Cover => get_distance_to_corner(size, center, ::std::cmp::max), + ShapeExtent::ClosestCorner => { + 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) } /// Determines the radius of an ellipse if it was not explictly provided. /// 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, center: &Point2D) -> Size2D { - use style::values::computed::image::SizeKeyword::*; match keyword { - ClosestSide | Contain => get_distance_to_sides(size, center, ::std::cmp::min), - FarthestSide => get_distance_to_sides(size, center, ::std::cmp::max), - ClosestCorner => get_ellipse_radius(size, center, ::std::cmp::min), - FarthestCorner | Cover => get_ellipse_radius(size, center, ::std::cmp::max), + ShapeExtent::ClosestSide | ShapeExtent::Contain => { + get_distance_to_sides(size, center, ::std::cmp::min) + }, + 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, bounds: &Rect, stops: &[GradientItem], - angle_or_corner: &AngleOrCorner, + direction: &LineDirection, repeating: bool, style: &ServoComputedValues) -> display_list::Gradient { - let angle = match *angle_or_corner { - AngleOrCorner::Angle(angle) => angle.radians(), - AngleOrCorner::Corner(horizontal, vertical) => { + let angle = match *direction { + LineDirection::Angle(angle) => angle.radians(), + LineDirection::Corner(horizontal, vertical) => { // 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 // two perpendicular angles. @@ -1171,16 +1182,18 @@ impl FragmentDisplayListBuilding for Fragment { let center = Point2D::new(specified(center.horizontal, bounds.size.width), specified(center.vertical, bounds.size.height)); let radius = match *shape { - EndingShape::Circle(LengthOrKeyword::Length(length)) - => Size2D::new(length, length), - EndingShape::Circle(LengthOrKeyword::Keyword(word)) - => convert_circle_size_keyword(word, &bounds.size, ¢er), - EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(horizontal, - vertical)) - => Size2D::new(specified(horizontal, bounds.size.width), - specified(vertical, bounds.size.height)), - EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(word)) - => convert_ellipse_size_keyword(word, &bounds.size, ¢er), + GenericEndingShape::Circle(Circle::Radius(length)) => { + Size2D::new(length, length) + }, + GenericEndingShape::Circle(Circle::Extent(extent)) => { + convert_circle_size_keyword(extent, &bounds.size, ¢er) + }, + GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => { + Size2D::new(specified(x, bounds.size.width), specified(y, bounds.size.height)) + }, + GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => { + convert_ellipse_size_keyword(extent, &bounds.size, ¢er) + }, }; let mut stops = convert_gradient_stops(stops, radius.width, style); diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 7dc333964e4..91de1643b95 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -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_FARTHEST_CORNER, NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE}; use gecko_bindings::structs::nsStyleCoord; - use values::computed::{AngleOrCorner, GradientKind, GradientShape, LengthOrKeyword}; - use values::computed::LengthOrPercentageOrKeyword; - use values::specified::SizeKeyword; + use values::computed::image::LineDirection; + use values::generics::image::{Circle, Ellipse, EndingShape, GradientKind, ShapeExtent}; use values::specified::position::{X, Y}; let stop_count = gradient.items.len(); @@ -204,7 +203,7 @@ impl nsStyleImage { } let gecko_gradient = match gradient.kind { - GradientKind::Linear(angle_or_corner) => { + GradientKind::Linear(direction) => { let gecko_gradient = unsafe { Gecko_CreateGradient(NS_STYLE_GRADIENT_SHAPE_LINEAR as u8, NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as u8, @@ -213,15 +212,15 @@ impl nsStyleImage { stop_count as u32) }; - match angle_or_corner { - AngleOrCorner::Angle(angle) => { + match direction { + LineDirection::Angle(angle) => { unsafe { (*gecko_gradient).mAngle.set(angle); (*gecko_gradient).mBgPosX.set_value(CoordDataValue::None); (*gecko_gradient).mBgPosY.set_value(CoordDataValue::None); } }, - AngleOrCorner::Corner(horiz, vert) => { + LineDirection::Corner(horiz, vert) => { let percent_x = match horiz { X::Left => 0.0, X::Right => 1.0, @@ -245,28 +244,28 @@ impl nsStyleImage { GradientKind::Radial(shape, position) => { let keyword_to_gecko_size = |keyword| { match keyword { - SizeKeyword::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, - SizeKeyword::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE, - SizeKeyword::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER, - SizeKeyword::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, - SizeKeyword::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, - SizeKeyword::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, + ShapeExtent::ClosestSide => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, + ShapeExtent::FarthestSide => NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE, + ShapeExtent::ClosestCorner => NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER, + ShapeExtent::FarthestCorner => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, + ShapeExtent::Contain => NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, + ShapeExtent::Cover => NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER, } }; let (gecko_shape, gecko_size) = match shape { - GradientShape::Circle(ref length) => { - let size = match *length { - LengthOrKeyword::Keyword(keyword) => { - keyword_to_gecko_size(keyword) + EndingShape::Circle(ref circle) => { + let size = match *circle { + Circle::Extent(extent) => { + keyword_to_gecko_size(extent) }, _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE, }; (NS_STYLE_GRADIENT_SHAPE_CIRCULAR as u8, size as u8) }, - GradientShape::Ellipse(ref length) => { - let size = match *length { - LengthOrPercentageOrKeyword::Keyword(keyword) => { - keyword_to_gecko_size(keyword) + EndingShape::Ellipse(ref ellipse) => { + let size = match *ellipse { + Ellipse::Extent(extent) => { + keyword_to_gecko_size(extent) }, _ => NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE, }; @@ -291,22 +290,19 @@ impl nsStyleImage { // Setting radius values depending shape match shape { - GradientShape::Circle(length) => { - if let LengthOrKeyword::Length(len) = length { - unsafe { - (*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(len.0)); - (*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(len.0)); - } + EndingShape::Circle(Circle::Radius(length)) => { + unsafe { + (*gecko_gradient).mRadiusX.set_value(CoordDataValue::Coord(length.0)); + (*gecko_gradient).mRadiusY.set_value(CoordDataValue::Coord(length.0)); } }, - GradientShape::Ellipse(length) => { - if let LengthOrPercentageOrKeyword::LengthOrPercentage(first_len, second_len) = length { - unsafe { - (*gecko_gradient).mRadiusX.set(first_len); - (*gecko_gradient).mRadiusY.set(second_len); - } + EndingShape::Ellipse(Ellipse::Radii(x, y)) => { + unsafe { + (*gecko_gradient).mRadiusX.set(x); + (*gecko_gradient).mRadiusY.set(y); } }, + _ => {}, } unsafe { (*gecko_gradient).mBgPosX.set(position.horizontal); diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index b31da88d338..2141880895a 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -11,23 +11,49 @@ use cssparser::Color as CSSColor; 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::{Image as GenericImage, ImageRect as GenericImageRect}; use values::computed::{Angle, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue}; 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}; -pub use values::specified::SizeKeyword; - /// Computed values for an image according to CSS-IMAGES. /// https://drafts.csswg.org/css-images/#image-values -pub type Image = GenericImage; +pub type Image = GenericImage; /// Computed values for a CSS gradient. /// https://drafts.csswg.org/css-images/#gradients -pub type Gradient = GenericGradient; +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; /// A computed gradient item. pub type GradientItem = GenericGradientItem; @@ -38,355 +64,65 @@ pub type ColorStop = GenericColorStop; /// Computed values for ImageRect. pub type ImageRect = GenericImageRect; -impl ToCss for Gradient { - fn to_css(&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-")); - } - 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 { +impl GenericLineDirection for LineDirection { + fn points_downwards(&self) -> bool { match *self { - specified::GradientKind::Linear(angle_or_corner) => { - GradientKind::Linear(angle_or_corner.to_computed_value(context)) - }, - specified::GradientKind::Radial(ref shape, ref position) => { - GradientKind::Radial(shape.to_computed_value(context), - position.to_computed_value(context)) - }, + LineDirection::Angle(angle) => angle.radians() == PI, + LineDirection::Corner(..) => false, } } - #[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 -/// https://drafts.csswg.org/css-images/#valdef-radial-gradient-ending-shape -#[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(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + fn to_css(&self, dest: &mut W, compat_mode: CompatMode) -> 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(()) - } -} - -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(&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(&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!() - } + LineDirection::Angle(ref angle) => angle.to_css(dest), + LineDirection::Corner(x, y) => { + if compat_mode == CompatMode::Modern { + dest.write_str("to ")?; } - } - } - } - - #[inline] - fn from_computed_value(computed: &AngleOrCorner) -> Self { - match *computed { - AngleOrCorner::Angle(ref angle) => { - specified::AngleOrCorner::Angle(specified::Angle::from_computed_value(angle)) + x.to_css(dest)?; + dest.write_str(" ")?; + y.to_css(dest) }, - AngleOrCorner::Corner(horizontal, vertical) => { - specified::AngleOrCorner::Corner(Some(horizontal), Some(vertical)) - } } } } -impl AngleOrCorner { - fn to_css(&self, dest: &mut W, mode: CompatMode) -> fmt::Result where W: fmt::Write { +impl ToComputedValue for SpecifiedLineDirection { + type ComputedValue = LineDirection; + + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { - AngleOrCorner::Angle(angle) => angle.to_css(dest), - AngleOrCorner::Corner(horizontal, vertical) => { - if mode == CompatMode::Modern { - try!(dest.write_str("to ")); - } - try!(horizontal.to_css(dest)); - try!(dest.write_str(" ")); - try!(vertical.to_css(dest)); - Ok(()) - } + SpecifiedLineDirection::Angle(ref angle) => { + LineDirection::Angle(angle.to_computed_value(context)) + }, + SpecifiedLineDirection::Horizontal(X::Left) => { + LineDirection::Angle(Angle::Degree(270.)) + }, + SpecifiedLineDirection::Horizontal(X::Right) => { + LineDirection::Angle(Angle::Degree(90.)) + }, + 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) + }, } } } diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 5addd13be6d..1bab68160be 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -12,8 +12,7 @@ use super::{Number, ToComputedValue, Context}; use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified}; use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength, ViewportPercentageLength}; -pub use super::image::{EndingShape as GradientShape, Gradient, GradientKind, Image}; -pub use super::image::{LengthOrKeyword, LengthOrPercentageOrKeyword}; +pub use super::image::Image; pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone}; impl ToComputedValue for specified::NoCalcLength { diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index f1721e552cd..56aeb62ee25 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -22,8 +22,7 @@ use super::specified::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as G pub use app_units::Au; pub use cssparser::Color as CSSColor; -pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientItem, LayerImage}; -pub use self::image::{GradientKind, Image, ImageRect, LengthOrKeyword, LengthOrPercentageOrKeyword}; +pub use self::image::{Gradient, GradientItem, LayerImage, LineDirection, Image, ImageRect}; pub use super::{Auto, Either, None_}; #[cfg(feature = "gecko")] pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index de644215acc..55bb8d20d43 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -19,13 +19,13 @@ use values::specified::url::SpecifiedUrl; /// [image]: https://drafts.csswg.org/css-images/#image-values #[derive(Clone, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum Image { +pub enum Image { /// A `` image. Url(SpecifiedUrl), /// A `` image. - Gradient(G), + Gradient(Gradient), /// A `-moz-image-rect` image - Rect(ImageRect), + Rect(ImageRect), /// A `-moz-element(# )` Element(Atom), } @@ -34,11 +34,11 @@ pub enum Image { /// https://drafts.csswg.org/css-images/#gradients #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct Gradient { +pub struct Gradient { /// Gradients can be linear or radial. - pub kind: K, + pub kind: GradientKind, /// The color stops and interpolation hints. - pub items: Vec>, + pub items: Vec>, /// True if this is a repeating gradient. pub repeating: bool, /// Compatibility mode. @@ -55,26 +55,76 @@ pub enum CompatMode { WebKit, } +/// A gradient kind. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum GradientKind { + /// A linear gradient. + Linear(LineDirection), + /// A radial gradient. + Radial(EndingShape, Position), +} + +/// A radial gradient's ending shape. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum EndingShape { + /// A circular gradient. + Circle(Circle), + /// An elliptic gradient. + Ellipse(Ellipse), +} + +/// A circle shape. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum Circle { + /// 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 { + /// 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. /// https://drafts.csswg.org/css-images-4/#color-stop-syntax #[derive(Clone, Copy, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum GradientItem { +pub enum GradientItem { /// A color stop. - ColorStop(ColorStop), + ColorStop(ColorStop), /// An interpolation hint. - InterpolationHint(L), + InterpolationHint(LengthOrPercentage), } /// A color stop. /// https://drafts.csswg.org/css-images/#typedef-color-stop-list #[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct ColorStop { +pub struct ColorStop { /// The color of this stop. - pub color: C, + pub color: Color, /// The position of this stop. - pub position: Option, + pub position: Option, } /// Values for `moz-image-rect`. @@ -83,16 +133,16 @@ pub struct ColorStop { #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[allow(missing_docs)] -pub struct ImageRect { +pub struct ImageRect { pub url: SpecifiedUrl, - pub top: C, - pub bottom: C, - pub right: C, - pub left: C, + pub top: NumberOrPercentage, + pub bottom: NumberOrPercentage, + pub right: NumberOrPercentage, + pub left: NumberOrPercentage, } -impl fmt::Debug for Image - where G: fmt::Debug, N: fmt::Debug, +impl fmt::Debug for Image + where G: fmt::Debug, R: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -108,8 +158,8 @@ impl fmt::Debug for Image } } -impl ToCss for Image - where G: ToCss, N: ToCss, +impl ToCss for Image + where G: ToCss, R: ToCss, { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { @@ -125,7 +175,7 @@ impl ToCss for Image } } -impl HasViewportPercentage for Image +impl HasViewportPercentage for Image where G: HasViewportPercentage { fn has_viewport_percentage(&self) -> bool { @@ -136,11 +186,11 @@ impl HasViewportPercentage for Image } } -impl ToComputedValue for Image - where G: ToComputedValue, N: ToComputedValue, +impl ToComputedValue for Image + where G: ToComputedValue, R: ToComputedValue, { type ComputedValue = Image<::ComputedValue, - ::ComputedValue>; + ::ComputedValue>; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { @@ -177,8 +227,64 @@ impl ToComputedValue for Image } } -impl HasViewportPercentage for Gradient - where K: HasViewportPercentage, L: HasViewportPercentage, +impl ToCss for Gradient + where D: LineDirection, L: ToCss, LoP: ToCss, P: ToCss, C: ToCss, +{ + fn to_css(&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 HasViewportPercentage for Gradient + where L: HasViewportPercentage, + LoP: HasViewportPercentage, + P: HasViewportPercentage, { fn has_viewport_percentage(&self) -> bool { self.kind.has_viewport_percentage() || @@ -186,12 +292,18 @@ impl HasViewportPercentage for Gradient } } -impl ToComputedValue for Gradient - where K: ToComputedValue, C: ToComputedValue, L: ToComputedValue, +impl ToComputedValue for Gradient + where D: ToComputedValue, + L: ToComputedValue, + LoP: ToComputedValue, + P: ToComputedValue, + C: ToComputedValue, { - type ComputedValue = Gradient<::ComputedValue, - ::ComputedValue, - ::ComputedValue>; + type ComputedValue = Gradient<::ComputedValue, + ::ComputedValue, + ::ComputedValue, +

::ComputedValue, + ::ComputedValue>; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { Gradient { @@ -212,6 +324,168 @@ impl ToComputedValue for Gradient } } +impl GradientKind { + fn label(&self) -> &str { + match *self { + GradientKind::Linear(..) => "linear", + GradientKind::Radial(..) => "radial", + } + } +} + +impl HasViewportPercentage for GradientKind + 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 ToComputedValue for GradientKind + where D: ToComputedValue, + L: ToComputedValue, + LoP: ToComputedValue, + P: ToComputedValue, +{ + type ComputedValue = GradientKind<::ComputedValue, + ::ComputedValue, + ::ComputedValue, +

::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(&self, dest: &mut W, compat_mode: CompatMode) -> fmt::Result + where W: fmt::Write; +} + +impl ToCss for EndingShape + where L: ToCss, LoP: ToCss, +{ + fn to_css(&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 HasViewportPercentage for EndingShape + 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 ToComputedValue for EndingShape + where L: ToComputedValue, LoP: ToComputedValue, +{ + type ComputedValue = EndingShape<::ComputedValue, + ::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 ToCss for GradientItem where C: ToCss, L: ToCss, { @@ -342,7 +616,7 @@ impl ToComputedValue for ImageRect fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { ImageRect { - url: self.url.clone(), + url: self.url.to_computed_value(context), top: self.top.to_computed_value(context), right: self.right.to_computed_value(context), bottom: self.bottom.to_computed_value(context), @@ -352,7 +626,7 @@ impl ToComputedValue for ImageRect fn from_computed_value(computed: &Self::ComputedValue) -> Self { ImageRect { - url: computed.url.clone(), + url: ToComputedValue::from_computed_value(&computed.url), top: ToComputedValue::from_computed_value(&computed.top), right: ToComputedValue::from_computed_value(&computed.right), bottom: ToComputedValue::from_computed_value(&computed.bottom), diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 01352fc187c..12a87c7913a 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -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; +pub type Image = GenericImage; /// Specified values for a CSS gradient. /// https://drafts.csswg.org/css-images/#gradients -pub type Gradient = GenericGradient; +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; /// A specified gradient item. pub type GradientItem = GenericGradientItem; @@ -77,103 +111,59 @@ impl Image { } } -impl ToCss for Gradient { - fn to_css(&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 { - fn parse(context: &ParserContext, input: &mut Parser, parse_kind: F) - -> Result<(GradientKind, Vec), ()> - where F: FnOnce(&ParserContext, &mut Parser) -> Result - { - 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 ``: - /// - /// 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 { + 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 ``: - /// - /// https://drafts.csswg.org/css-images/#radial-gradients - Radial(EndingShape, Position), + fn parse_radial(context: &ParserContext, + input: &mut Parser, + compat_mode: CompatMode) + -> Result { + 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 { - 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 { - 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(&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 { + 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 { + 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 { - 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 - (EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(SizeKeyword::FarthestCorner)), position) - } else if let Ok((first, second)) = input.try(|i| parse_two_length(context, i)) { - // Handle ? ? - 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 ? ? - 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 ? ? - 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 ? ? - if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() { - // Handle ? ? - 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 ? ? - 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 { - 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 ? - 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 ? - if input.try(|input| input.expect_ident_matching("ellipse")).is_ok() { - // Handle ? - 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 ? - 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 { + 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 { - 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, Option), - None, -} - -impl AngleOrCorner { - fn to_css(&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 { Ok(ColorStop { @@ -447,115 +427,6 @@ impl Parse for ColorStop { } } -/// Determines whether the gradient's ending shape is a circle or an ellipse. -/// If is omitted, the ending shape defaults to a circle -/// if the is a single , 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(&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(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result - where F: Fn(&mut Parser) -> Result - { - 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(&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(context: &ParserContext, input: &mut Parser, parse_size_keyword: F) -> Result - where F: Fn(&mut Parser) -> Result - { - 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(&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 { - match try!(SizeKeyword::parse(input)) { - SizeKeyword::Contain | SizeKeyword::Cover => Err(()), - keyword => Ok(keyword), - } - } -} - /// Specified values for none | | . #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 13eb17b6a6f..27e5494b9f5 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -24,8 +24,8 @@ use values::computed::{ComputedValueAsSpecified, Context}; use values::specified::calc::CalcNode; pub use values::specified::calc::CalcLengthOrPercentage; -pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient}; -pub use super::image::{GradientKind, Image, LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword}; +pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient}; +pub use super::image::{GradientKind, Image}; /// Number of app units per pixel pub const AU_PER_PX: CSSFloat = 60.; diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index d7245b25d2a..c0e9bfbd0fb 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -28,9 +28,8 @@ use values::specified::calc::CalcNode; pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; pub use self::color::Color; 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::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword}; pub use self::length::AbsoluteLength; pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage}; pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto}; diff --git a/tests/unit/style/parsing/image.rs b/tests/unit/style/parsing/image.rs index 708c50b0d93..b4956e8edb7 100644 --- a/tests/unit/style/parsing/image.rs +++ b/tests/unit/style/parsing/image.rs @@ -2,16 +2,7 @@ * 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/. */ -use euclid::size::TypedSize2D; 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_traits::ToCss; @@ -37,101 +28,77 @@ fn test_linear_gradient() { // Parsing without and 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] fn test_radial_gradient() { // 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(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)", "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)", - "radial-gradient(ellipse closest-side at 20px 30px, red, green)"); + "radial-gradient(closest-side at 20px 30px, red, green)"); // Parsing with and reversed 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)"); 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)", - "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)", - "radial-gradient(ellipse 30px 40px at center center, red, green)"); + "radial-gradient(30px 40px at center center, red, green)"); // Parsing without assert_roundtrip_with_context!(Image::parse, "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, "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, - "radial-gradient(circle at 20px 30px, red, green)", - "radial-gradient(circle farthest-corner at 20px 30px, red, green)"); + "radial-gradient(circle at 20px 30px, red, green)"); assert_roundtrip_with_context!(Image::parse, "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 assert_roundtrip_with_context!(Image::parse, - "radial-gradient(20px at 20px 30px, red, green)", - "radial-gradient(circle 20px at 20px 30px, red, green)"); + "radial-gradient(20px at 20px 30px, red, green)"); assert_roundtrip_with_context!(Image::parse, - "radial-gradient(20px 30px at left center, red, green)", - "radial-gradient(ellipse 20px 30px at left center, red, green)"); + "radial-gradient(20px 30px at left center, red, green)"); assert_roundtrip_with_context!(Image::parse, "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, "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, "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, "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 and assert_roundtrip_with_context!(Image::parse, "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, - "radial-gradient(at center bottom, red, green)", - "radial-gradient(ellipse farthest-corner at center bottom, red, green)"); + "radial-gradient(at center bottom, red, green)"); assert_roundtrip_with_context!(Image::parse, - "radial-gradient(at 40px 50px, red, green)", - "radial-gradient(ellipse farthest-corner at 40px 50px, red, green)"); + "radial-gradient(at 40px 50px, red, green)"); // Parsing with just color stops assert_roundtrip_with_context!(Image::parse, "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 assert_roundtrip_with_context!(Image::parse, "repeating-radial-gradient(red, green)", - "repeating-radial-gradient(ellipse farthest-corner at center center, red, green)"); + "repeating-radial-gradient(at center center, red, green)"); }