/* 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; /// A specified `shape-outside` value. pub type ShapeOutside = generic::GenericShapeOutside; /// A specified basic shape. pub type BasicShape = generic::GenericBasicShape< HorizontalPosition, VerticalPosition, LengthPercentage, NonNegativeLengthPercentage, >; /// The specified value of `inset()` pub type InsetRect = generic::InsetRect; /// A specified circle. pub type Circle = generic::Circle; /// A specified ellipse. pub type Ellipse = generic::Ellipse; /// The specified value of `ShapeRadius` pub type ShapeRadius = generic::ShapeRadius; /// The specified value of `Polygon` pub type Polygon = generic::GenericPolygon; /// 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, ReferenceBox) -> R, to_reference_box: impl FnOnce(ReferenceBox) -> R, ) -> Result> where ReferenceBox: Default + Parse, { fn parse_component( context: &ParserContext, input: &mut Parser, component: &mut Option, ) -> 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> { 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> { // 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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 }) } }