/* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ //! CSS handling for the specified value of //! [`image`][image]s //! //! [image]: https://drafts.csswg.org/css-images/#image-values use cssparser::Parser; use parser::{Parse, ParserContext}; use servo_url::ServoUrl; use std::fmt; use style_traits::ToCss; use values::specified::{Angle, CSSColor, Length, LengthOrPercentage}; use values::specified::position::Position; use values::specified::url::{SpecifiedUrl, UrlExtraData}; /// Specified values for an image according to CSS-IMAGES. /// https://drafts.csswg.org/css-images/#image-values #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum Image { /// A `` image. Url(SpecifiedUrl), /// A `` image. Gradient(Gradient), } impl ToCss for Image { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { Image::Url(ref url_value) => url_value.to_css(dest), Image::Gradient(ref gradient) => gradient.to_css(dest), } } } impl Image { #[allow(missing_docs)] pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) { return Ok(Image::Url(url)); } Ok(Image::Gradient(try!(Gradient::parse_function(context, input)))) } /// Creates an already specified image value from an already resolved URL /// for insertion in the cascade. pub fn for_cascade(url: ServoUrl, extra_data: UrlExtraData) -> Self { Image::Url(SpecifiedUrl::for_cascade(url, extra_data)) } } /// Specified values for a CSS gradient. /// https://drafts.csswg.org/css-images/#gradients #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Gradient { /// The color stops. pub stops: Vec, /// True if this is a repeating gradient. pub repeating: bool, /// Gradients can be linear or radial. pub gradient_kind: GradientKind, } impl ToCss for Gradient { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { if self.repeating { try!(dest.write_str("repeating-")); } let mut skipcomma = false; match self.gradient_kind { GradientKind::Linear(angle_or_corner) => { try!(dest.write_str("linear-gradient(")); try!(angle_or_corner.to_css(dest)); if angle_or_corner == AngleOrCorner::None { skipcomma = true; } }, GradientKind::Radial(ref shape, ref position) => { try!(dest.write_str("radial-gradient(")); try!(shape.to_css(dest)); try!(dest.write_str(" at ")); try!(position.to_css(dest)); }, } for stop in &self.stops { if !skipcomma { try!(dest.write_str(", ")); } else { skipcomma = false; } try!(stop.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 { let mut repeating = false; let (gradient_kind, stops) = match_ignore_ascii_case! { try!(input.expect_function()), "linear-gradient" => { try!(input.parse_nested_block(|input| { let kind = try!(GradientKind::parse_linear(context, input)); let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i))); Ok((kind, stops)) }) ) }, "repeating-linear-gradient" => { repeating = true; try!(input.parse_nested_block(|input| { let kind = try!(GradientKind::parse_linear(context, input)); let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i))); Ok((kind, stops)) }) ) }, "radial-gradient" => { try!(input.parse_nested_block(|input| { let kind = try!(GradientKind::parse_radial(context, input)); let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i))); Ok((kind, stops)) }) ) }, "repeating-radial-gradient" => { repeating = true; try!(input.parse_nested_block(|input| { let kind = try!(GradientKind::parse_radial(context, input)); let stops = try!(input.parse_comma_separated(|i| ColorStop::parse(context, i))); Ok((kind, stops)) }) ) }, _ => { return Err(()); } }; // https://drafts.csswg.org/css-images/#typedef-color-stop-list if stops.len() < 2 { return Err(()) } Ok(Gradient { stops: stops, repeating: repeating, gradient_kind: gradient_kind, }) } } /// 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), /// A ``: /// /// https://drafts.csswg.org/css-images/#radial-gradients Radial(EndingShape, Position), } impl GradientKind { /// Parses a linear gradient kind from the given arguments. pub fn parse_linear(context: &ParserContext, input: &mut Parser) -> Result { let angle_or_corner = try!(AngleOrCorner::parse(context, input)); Ok(GradientKind::Linear(angle_or_corner)) } /// Parses a radial gradient from the given arguments. pub fn parse_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) { // Handle ? ? let shape = 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)) }; (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)) .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)) .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)) } } 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 ToCss for AngleOrCorner { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { AngleOrCorner::None => Ok(()), AngleOrCorner::Angle(angle) => angle.to_css(dest), AngleOrCorner::Corner(horizontal, vertical) => { 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 AngleOrCorner { fn parse(context: &ParserContext, input: &mut Parser) -> Result { if input.try(|input| input.expect_ident_matching("to")).is_ok() { let (horizontal, vertical) = if let Ok(value) = input.try(HorizontalDirection::parse) { (Some(value), input.try(VerticalDirection::parse).ok()) } else { let value = try!(VerticalDirection::parse(input)); (input.try(HorizontalDirection::parse).ok(), Some(value)) }; try!(input.expect_comma()); Ok(AngleOrCorner::Corner(horizontal, vertical)) } else if let Ok(angle) = input.try(|i| Angle::parse(context, i)) { try!(input.expect_comma()); Ok(AngleOrCorner::Angle(angle)) } else { Ok(AngleOrCorner::None) } } } /// Specified values for one color stop in a linear gradient. /// https://drafts.csswg.org/css-images/#typedef-color-stop-list #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct ColorStop { /// The color of this stop. pub color: CSSColor, /// The position of this stop. If not specified, this stop is placed halfway between the /// point that precedes it and the point that follows it. pub position: Option, } impl ToCss for ColorStop { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(self.color.to_css(dest)); if let Some(ref position) = self.position { try!(dest.write_str(" ")); try!(position.to_css(dest)); } Ok(()) } } define_css_keyword_enum!(HorizontalDirection: "left" => Left, "right" => Right); define_css_keyword_enum!(VerticalDirection: "top" => Top, "bottom" => Bottom); impl Parse for ColorStop { fn parse(context: &ParserContext, input: &mut Parser) -> Result { Ok(ColorStop { color: try!(CSSColor::parse(context, input)), position: input.try(|i| LengthOrPercentage::parse(context, i)).ok(), }) } } /// 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 Parse for LengthOrKeyword { fn parse(context: &ParserContext, input: &mut Parser) -> Result { if let Ok(keyword) = input.try(SizeKeyword::parse) { 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 Parse for LengthOrPercentageOrKeyword { fn parse(context: &ParserContext, input: &mut Parser) -> Result { if let Ok(keyword) = input.try(SizeKeyword::parse) { 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);