1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 //! CSS handling for the specified value of
6 //! [`basic-shape`][basic-shape]s
7 //!
8 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
9 
10 use crate::parser::{Parse, ParserContext};
11 use crate::values::generics::basic_shape as generic;
12 use crate::values::generics::basic_shape::{Path, PolygonCoord};
13 use crate::values::generics::rect::Rect;
14 use crate::values::specified::border::BorderRadius;
15 use crate::values::specified::image::Image;
16 use crate::values::specified::position::{HorizontalPosition, Position, VerticalPosition};
17 use crate::values::specified::url::SpecifiedUrl;
18 use crate::values::specified::SVGPathData;
19 use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage};
20 use crate::Zero;
21 use cssparser::Parser;
22 use style_traits::{ParseError, StyleParseErrorKind};
23 
24 /// A specified alias for FillRule.
25 pub use crate::values::generics::basic_shape::FillRule;
26 
27 /// A specified `clip-path` value.
28 pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>;
29 
30 /// A specified `shape-outside` value.
31 pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>;
32 
33 /// A specified basic shape.
34 pub type BasicShape = generic::GenericBasicShape<
35     HorizontalPosition,
36     VerticalPosition,
37     LengthPercentage,
38     NonNegativeLengthPercentage,
39 >;
40 
41 /// The specified value of `inset()`
42 pub type InsetRect = generic::InsetRect<LengthPercentage, NonNegativeLengthPercentage>;
43 
44 /// A specified circle.
45 pub type Circle =
46     generic::Circle<HorizontalPosition, VerticalPosition, NonNegativeLengthPercentage>;
47 
48 /// A specified ellipse.
49 pub type Ellipse =
50     generic::Ellipse<HorizontalPosition, VerticalPosition, NonNegativeLengthPercentage>;
51 
52 /// The specified value of `ShapeRadius`
53 pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>;
54 
55 /// The specified value of `Polygon`
56 pub type Polygon = generic::GenericPolygon<LengthPercentage>;
57 
58 /// A helper for both clip-path and shape-outside parsing of shapes.
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,59 fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
60     context: &ParserContext,
61     input: &mut Parser<'i, 't>,
62     to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
63     to_reference_box: impl FnOnce(ReferenceBox) -> R,
64 ) -> Result<R, ParseError<'i>>
65 where
66     ReferenceBox: Default + Parse,
67 {
68     fn parse_component<U: Parse>(
69         context: &ParserContext,
70         input: &mut Parser,
71         component: &mut Option<U>,
72     ) -> bool {
73         if component.is_some() {
74             return false; // already parsed this component
75         }
76 
77         *component = input.try_parse(|i| U::parse(context, i)).ok();
78         component.is_some()
79     }
80 
81     let mut shape = None;
82     let mut ref_box = None;
83 
84     while parse_component(context, input, &mut shape) ||
85         parse_component(context, input, &mut ref_box)
86     {
87         //
88     }
89 
90     if let Some(shp) = shape {
91         return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
92     }
93 
94     match ref_box {
95         Some(r) => Ok(to_reference_box(r)),
96         None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
97     }
98 }
99 
100 impl Parse for ClipPath {
101     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>102     fn parse<'i, 't>(
103         context: &ParserContext,
104         input: &mut Parser<'i, 't>,
105     ) -> Result<Self, ParseError<'i>> {
106         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
107             return Ok(ClipPath::None);
108         }
109 
110         if let Ok(p) = input.try_parse(|i| Path::parse(context, i)) {
111             return Ok(ClipPath::Path(p));
112         }
113 
114         if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
115             return Ok(ClipPath::Url(url));
116         }
117 
118         parse_shape_or_box(context, input, ClipPath::Shape, ClipPath::Box)
119     }
120 }
121 
122 impl Parse for ShapeOutside {
123     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>124     fn parse<'i, 't>(
125         context: &ParserContext,
126         input: &mut Parser<'i, 't>,
127     ) -> Result<Self, ParseError<'i>> {
128         // Need to parse this here so that `Image::parse_with_cors_anonymous`
129         // doesn't parse it.
130         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
131             return Ok(ShapeOutside::None);
132         }
133 
134         if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
135             debug_assert_ne!(image, Image::None);
136             return Ok(ShapeOutside::Image(image));
137         }
138 
139         parse_shape_or_box(context, input, ShapeOutside::Shape, ShapeOutside::Box)
140     }
141 }
142 
143 impl Parse for BasicShape {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>144     fn parse<'i, 't>(
145         context: &ParserContext,
146         input: &mut Parser<'i, 't>,
147     ) -> Result<Self, ParseError<'i>> {
148         let location = input.current_source_location();
149         let function = input.expect_function()?.clone();
150         input.parse_nested_block(move |i| {
151             (match_ignore_ascii_case! { &function,
152                 "inset" => return InsetRect::parse_function_arguments(context, i).map(generic::BasicShape::Inset),
153                 "circle" => return Circle::parse_function_arguments(context, i).map(generic::BasicShape::Circle),
154                 "ellipse" => return Ellipse::parse_function_arguments(context, i).map(generic::BasicShape::Ellipse),
155                 "polygon" => return Polygon::parse_function_arguments(context, i).map(generic::BasicShape::Polygon),
156                 _ => Err(())
157             }).map_err(|()| {
158                 location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))
159             })
160         })
161     }
162 }
163 
164 impl Parse for InsetRect {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>165     fn parse<'i, 't>(
166         context: &ParserContext,
167         input: &mut Parser<'i, 't>,
168     ) -> Result<Self, ParseError<'i>> {
169         input.expect_function_matching("inset")?;
170         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
171     }
172 }
173 
174 impl InsetRect {
175     /// Parse the inner function arguments of `inset()`
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>176     fn parse_function_arguments<'i, 't>(
177         context: &ParserContext,
178         input: &mut Parser<'i, 't>,
179     ) -> Result<Self, ParseError<'i>> {
180         let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
181         let round = if input
182             .try_parse(|i| i.expect_ident_matching("round"))
183             .is_ok()
184         {
185             BorderRadius::parse(context, input)?
186         } else {
187             BorderRadius::zero()
188         };
189         Ok(generic::InsetRect { rect, round })
190     }
191 }
192 
193 impl Parse for Circle {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>194     fn parse<'i, 't>(
195         context: &ParserContext,
196         input: &mut Parser<'i, 't>,
197     ) -> Result<Self, ParseError<'i>> {
198         input.expect_function_matching("circle")?;
199         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
200     }
201 }
202 
203 impl Circle {
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>204     fn parse_function_arguments<'i, 't>(
205         context: &ParserContext,
206         input: &mut Parser<'i, 't>,
207     ) -> Result<Self, ParseError<'i>> {
208         let radius = input
209             .try_parse(|i| ShapeRadius::parse(context, i))
210             .unwrap_or_default();
211         let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
212             Position::parse(context, input)?
213         } else {
214             Position::center()
215         };
216 
217         Ok(generic::Circle { radius, position })
218     }
219 }
220 
221 impl Parse for Ellipse {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>222     fn parse<'i, 't>(
223         context: &ParserContext,
224         input: &mut Parser<'i, 't>,
225     ) -> Result<Self, ParseError<'i>> {
226         input.expect_function_matching("ellipse")?;
227         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
228     }
229 }
230 
231 impl Ellipse {
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>232     fn parse_function_arguments<'i, 't>(
233         context: &ParserContext,
234         input: &mut Parser<'i, 't>,
235     ) -> Result<Self, ParseError<'i>> {
236         let (a, b) = input
237             .try_parse(|i| -> Result<_, ParseError> {
238                 Ok((
239                     ShapeRadius::parse(context, i)?,
240                     ShapeRadius::parse(context, i)?,
241                 ))
242             })
243             .unwrap_or_default();
244         let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
245             Position::parse(context, input)?
246         } else {
247             Position::center()
248         };
249 
250         Ok(generic::Ellipse {
251             semiaxis_x: a,
252             semiaxis_y: b,
253             position: position,
254         })
255     }
256 }
257 
258 impl Parse for Polygon {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>259     fn parse<'i, 't>(
260         context: &ParserContext,
261         input: &mut Parser<'i, 't>,
262     ) -> Result<Self, ParseError<'i>> {
263         input.expect_function_matching("polygon")?;
264         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
265     }
266 }
267 
268 impl Polygon {
269     /// Parse the inner arguments of a `polygon` function.
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>270     fn parse_function_arguments<'i, 't>(
271         context: &ParserContext,
272         input: &mut Parser<'i, 't>,
273     ) -> Result<Self, ParseError<'i>> {
274         let fill = input
275             .try_parse(|i| -> Result<_, ParseError> {
276                 let fill = FillRule::parse(i)?;
277                 i.expect_comma()?; // only eat the comma if there is something before it
278                 Ok(fill)
279             })
280             .unwrap_or_default();
281 
282         let coordinates = input
283             .parse_comma_separated(|i| {
284                 Ok(PolygonCoord(
285                     LengthPercentage::parse(context, i)?,
286                     LengthPercentage::parse(context, i)?,
287                 ))
288             })?
289             .into();
290 
291         Ok(Polygon { fill, coordinates })
292     }
293 }
294 
295 impl Parse for Path {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>296     fn parse<'i, 't>(
297         context: &ParserContext,
298         input: &mut Parser<'i, 't>,
299     ) -> Result<Self, ParseError<'i>> {
300         input.expect_function_matching("path")?;
301         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
302     }
303 }
304 
305 impl Path {
306     /// Parse the inner arguments of a `path` function.
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>307     fn parse_function_arguments<'i, 't>(
308         context: &ParserContext,
309         input: &mut Parser<'i, 't>,
310     ) -> Result<Self, ParseError<'i>> {
311         let fill = input
312             .try_parse(|i| -> Result<_, ParseError> {
313                 let fill = FillRule::parse(i)?;
314                 i.expect_comma()?;
315                 Ok(fill)
316             })
317             .unwrap_or_default();
318         let path = SVGPathData::parse(context, input)?;
319         Ok(Path { fill, path })
320     }
321 }
322