diff options
Diffstat (limited to '')
-rw-r--r-- | servo/components/style/values/specified/basic_shape.rs | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/servo/components/style/values/specified/basic_shape.rs b/servo/components/style/values/specified/basic_shape.rs new file mode 100644 index 0000000000..c085446c64 --- /dev/null +++ b/servo/components/style/values/specified/basic_shape.rs @@ -0,0 +1,321 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! CSS handling for the specified value of +//! [`basic-shape`][basic-shape]s +//! +//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape + +use crate::parser::{Parse, ParserContext}; +use crate::values::generics::basic_shape as generic; +use crate::values::generics::basic_shape::{Path, PolygonCoord}; +use crate::values::generics::rect::Rect; +use crate::values::specified::border::BorderRadius; +use crate::values::specified::image::Image; +use crate::values::specified::position::{HorizontalPosition, Position, VerticalPosition}; +use crate::values::specified::url::SpecifiedUrl; +use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData}; +use crate::Zero; +use cssparser::Parser; +use style_traits::{ParseError, StyleParseErrorKind}; + +/// A specified alias for FillRule. +pub use crate::values::generics::basic_shape::FillRule; + +/// A specified `clip-path` value. +pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>; + +/// A specified `shape-outside` value. +pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>; + +/// A specified basic shape. +pub type BasicShape = generic::GenericBasicShape< + HorizontalPosition, + VerticalPosition, + LengthPercentage, + NonNegativeLengthPercentage, +>; + +/// The specified value of `inset()` +pub type InsetRect = generic::InsetRect<LengthPercentage, NonNegativeLengthPercentage>; + +/// A specified circle. +pub type Circle = + generic::Circle<HorizontalPosition, VerticalPosition, NonNegativeLengthPercentage>; + +/// A specified ellipse. +pub type Ellipse = + generic::Ellipse<HorizontalPosition, VerticalPosition, NonNegativeLengthPercentage>; + +/// The specified value of `ShapeRadius` +pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>; + +/// The specified value of `Polygon` +pub type Polygon = generic::GenericPolygon<LengthPercentage>; + +/// A helper for both clip-path and shape-outside parsing of shapes. +fn parse_shape_or_box<'i, 't, R, ReferenceBox>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R, + to_reference_box: impl FnOnce(ReferenceBox) -> R, +) -> Result<R, ParseError<'i>> +where + ReferenceBox: Default + Parse, +{ + fn parse_component<U: Parse>( + context: &ParserContext, + input: &mut Parser, + component: &mut Option<U>, + ) -> bool { + if component.is_some() { + return false; // already parsed this component + } + + *component = input.try_parse(|i| U::parse(context, i)).ok(); + component.is_some() + } + + let mut shape = None; + let mut ref_box = None; + + while parse_component(context, input, &mut shape) || + parse_component(context, input, &mut ref_box) + { + // + } + + if let Some(shp) = shape { + return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default())); + } + + match ref_box { + Some(r) => Ok(to_reference_box(r)), + None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } +} + +impl Parse for ClipPath { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(ClipPath::None); + } + + if let Ok(p) = input.try_parse(|i| Path::parse(context, i)) { + return Ok(ClipPath::Path(p)); + } + + if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) { + return Ok(ClipPath::Url(url)); + } + + parse_shape_or_box(context, input, ClipPath::Shape, ClipPath::Box) + } +} + +impl Parse for ShapeOutside { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + // Need to parse this here so that `Image::parse_with_cors_anonymous` + // doesn't parse it. + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(ShapeOutside::None); + } + + if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) { + debug_assert_ne!(image, Image::None); + return Ok(ShapeOutside::Image(image)); + } + + parse_shape_or_box(context, input, ShapeOutside::Shape, ShapeOutside::Box) + } +} + +impl Parse for BasicShape { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); + let function = input.expect_function()?.clone(); + input.parse_nested_block(move |i| { + (match_ignore_ascii_case! { &function, + "inset" => return InsetRect::parse_function_arguments(context, i).map(generic::BasicShape::Inset), + "circle" => return Circle::parse_function_arguments(context, i).map(generic::BasicShape::Circle), + "ellipse" => return Ellipse::parse_function_arguments(context, i).map(generic::BasicShape::Ellipse), + "polygon" => return Polygon::parse_function_arguments(context, i).map(generic::BasicShape::Polygon), + _ => Err(()) + }).map_err(|()| { + location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone())) + }) + }) + } +} + +impl Parse for InsetRect { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_function_matching("inset")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl InsetRect { + /// Parse the inner function arguments of `inset()` + fn parse_function_arguments<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let rect = Rect::parse_with(context, input, LengthPercentage::parse)?; + let round = if input + .try_parse(|i| i.expect_ident_matching("round")) + .is_ok() + { + BorderRadius::parse(context, input)? + } else { + BorderRadius::zero() + }; + Ok(generic::InsetRect { rect, round }) + } +} + +impl Parse for Circle { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_function_matching("circle")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl Circle { + fn parse_function_arguments<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let radius = input + .try_parse(|i| ShapeRadius::parse(context, i)) + .unwrap_or_default(); + let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() { + Position::parse(context, input)? + } else { + Position::center() + }; + + Ok(generic::Circle { radius, position }) + } +} + +impl Parse for Ellipse { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_function_matching("ellipse")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl Ellipse { + fn parse_function_arguments<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let (a, b) = input + .try_parse(|i| -> Result<_, ParseError> { + Ok(( + ShapeRadius::parse(context, i)?, + ShapeRadius::parse(context, i)?, + )) + }) + .unwrap_or_default(); + let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() { + Position::parse(context, input)? + } else { + Position::center() + }; + + Ok(generic::Ellipse { + semiaxis_x: a, + semiaxis_y: b, + position: position, + }) + } +} + +impl Parse for Polygon { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_function_matching("polygon")?; + input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) + } +} + +impl Polygon { + /// Parse the inner arguments of a `polygon` function. + fn parse_function_arguments<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let fill = input + .try_parse(|i| -> Result<_, ParseError> { + let fill = FillRule::parse(i)?; + i.expect_comma()?; // only eat the comma if there is something before it + Ok(fill) + }) + .unwrap_or_default(); + + let coordinates = input + .parse_comma_separated(|i| { + Ok(PolygonCoord( + LengthPercentage::parse(context, i)?, + LengthPercentage::parse(context, i)?, + )) + })? + .into(); + + Ok(Polygon { fill, coordinates }) + } +} + +impl Parse for Path { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_function_matching("path")?; + input.parse_nested_block(Self::parse_function_arguments) + } +} + +impl Path { + /// Parse the inner arguments of a `path` function. + fn parse_function_arguments<'i, 't>( + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + use crate::values::specified::svg_path::AllowEmpty; + + let fill = input + .try_parse(|i| -> Result<_, ParseError> { + let fill = FillRule::parse(i)?; + i.expect_comma()?; + Ok(fill) + }) + .unwrap_or_default(); + let path = SVGPathData::parse(input, AllowEmpty::No)?; + Ok(Path { fill, path }) + } +} |