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 #[cfg(feature = "gecko")]
is_clip_path_path_enabled(context: &ParserContext) -> bool59 fn is_clip_path_path_enabled(context: &ParserContext) -> bool {
60     context.chrome_rules_enabled() || static_prefs::pref!("layout.css.clip-path-path.enabled")
61 }
62 #[cfg(feature = "servo")]
is_clip_path_path_enabled(_: &ParserContext) -> bool63 fn is_clip_path_path_enabled(_: &ParserContext) -> bool {
64     false
65 }
66 
67 /// 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,68 fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
69     context: &ParserContext,
70     input: &mut Parser<'i, 't>,
71     to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R,
72     to_reference_box: impl FnOnce(ReferenceBox) -> R,
73 ) -> Result<R, ParseError<'i>>
74 where
75     ReferenceBox: Default + Parse,
76 {
77     fn parse_component<U: Parse>(
78         context: &ParserContext,
79         input: &mut Parser,
80         component: &mut Option<U>,
81     ) -> bool {
82         if component.is_some() {
83             return false; // already parsed this component
84         }
85 
86         *component = input.try_parse(|i| U::parse(context, i)).ok();
87         component.is_some()
88     }
89 
90     let mut shape = None;
91     let mut ref_box = None;
92 
93     while parse_component(context, input, &mut shape) ||
94         parse_component(context, input, &mut ref_box)
95     {
96         //
97     }
98 
99     if let Some(shp) = shape {
100         return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default()));
101     }
102 
103     match ref_box {
104         Some(r) => Ok(to_reference_box(r)),
105         None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
106     }
107 }
108 
109 impl Parse for ClipPath {
110     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>111     fn parse<'i, 't>(
112         context: &ParserContext,
113         input: &mut Parser<'i, 't>,
114     ) -> Result<Self, ParseError<'i>> {
115         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
116             return Ok(ClipPath::None);
117         }
118 
119         if is_clip_path_path_enabled(context) {
120             if let Ok(p) = input.try_parse(|i| Path::parse(context, i)) {
121                 return Ok(ClipPath::Path(p));
122             }
123         }
124 
125         if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
126             return Ok(ClipPath::Url(url));
127         }
128 
129         parse_shape_or_box(context, input, ClipPath::Shape, ClipPath::Box)
130     }
131 }
132 
133 impl Parse for ShapeOutside {
134     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>135     fn parse<'i, 't>(
136         context: &ParserContext,
137         input: &mut Parser<'i, 't>,
138     ) -> Result<Self, ParseError<'i>> {
139         // Need to parse this here so that `Image::parse_with_cors_anonymous`
140         // doesn't parse it.
141         if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
142             return Ok(ShapeOutside::None);
143         }
144 
145         if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) {
146             debug_assert_ne!(image, Image::None);
147             return Ok(ShapeOutside::Image(image));
148         }
149 
150         parse_shape_or_box(context, input, ShapeOutside::Shape, ShapeOutside::Box)
151     }
152 }
153 
154 impl Parse for BasicShape {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>155     fn parse<'i, 't>(
156         context: &ParserContext,
157         input: &mut Parser<'i, 't>,
158     ) -> Result<Self, ParseError<'i>> {
159         let location = input.current_source_location();
160         let function = input.expect_function()?.clone();
161         input.parse_nested_block(move |i| {
162             (match_ignore_ascii_case! { &function,
163                 "inset" => return InsetRect::parse_function_arguments(context, i).map(generic::BasicShape::Inset),
164                 "circle" => return Circle::parse_function_arguments(context, i).map(generic::BasicShape::Circle),
165                 "ellipse" => return Ellipse::parse_function_arguments(context, i).map(generic::BasicShape::Ellipse),
166                 "polygon" => return Polygon::parse_function_arguments(context, i).map(generic::BasicShape::Polygon),
167                 _ => Err(())
168             }).map_err(|()| {
169                 location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))
170             })
171         })
172     }
173 }
174 
175 impl Parse for InsetRect {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>176     fn parse<'i, 't>(
177         context: &ParserContext,
178         input: &mut Parser<'i, 't>,
179     ) -> Result<Self, ParseError<'i>> {
180         input.expect_function_matching("inset")?;
181         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
182     }
183 }
184 
185 impl InsetRect {
186     /// Parse the inner function arguments of `inset()`
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>187     fn parse_function_arguments<'i, 't>(
188         context: &ParserContext,
189         input: &mut Parser<'i, 't>,
190     ) -> Result<Self, ParseError<'i>> {
191         let rect = Rect::parse_with(context, input, LengthPercentage::parse)?;
192         let round = if input
193             .try_parse(|i| i.expect_ident_matching("round"))
194             .is_ok()
195         {
196             BorderRadius::parse(context, input)?
197         } else {
198             BorderRadius::zero()
199         };
200         Ok(generic::InsetRect { rect, round })
201     }
202 }
203 
204 impl Parse for Circle {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>205     fn parse<'i, 't>(
206         context: &ParserContext,
207         input: &mut Parser<'i, 't>,
208     ) -> Result<Self, ParseError<'i>> {
209         input.expect_function_matching("circle")?;
210         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
211     }
212 }
213 
214 impl Circle {
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>215     fn parse_function_arguments<'i, 't>(
216         context: &ParserContext,
217         input: &mut Parser<'i, 't>,
218     ) -> Result<Self, ParseError<'i>> {
219         let radius = input
220             .try_parse(|i| ShapeRadius::parse(context, i))
221             .unwrap_or_default();
222         let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
223             Position::parse(context, input)?
224         } else {
225             Position::center()
226         };
227 
228         Ok(generic::Circle { radius, position })
229     }
230 }
231 
232 impl Parse for Ellipse {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>233     fn parse<'i, 't>(
234         context: &ParserContext,
235         input: &mut Parser<'i, 't>,
236     ) -> Result<Self, ParseError<'i>> {
237         input.expect_function_matching("ellipse")?;
238         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
239     }
240 }
241 
242 impl Ellipse {
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>243     fn parse_function_arguments<'i, 't>(
244         context: &ParserContext,
245         input: &mut Parser<'i, 't>,
246     ) -> Result<Self, ParseError<'i>> {
247         let (a, b) = input
248             .try_parse(|i| -> Result<_, ParseError> {
249                 Ok((
250                     ShapeRadius::parse(context, i)?,
251                     ShapeRadius::parse(context, i)?,
252                 ))
253             })
254             .unwrap_or_default();
255         let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() {
256             Position::parse(context, input)?
257         } else {
258             Position::center()
259         };
260 
261         Ok(generic::Ellipse {
262             semiaxis_x: a,
263             semiaxis_y: b,
264             position: position,
265         })
266     }
267 }
268 
269 impl Parse for Polygon {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>270     fn parse<'i, 't>(
271         context: &ParserContext,
272         input: &mut Parser<'i, 't>,
273     ) -> Result<Self, ParseError<'i>> {
274         input.expect_function_matching("polygon")?;
275         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
276     }
277 }
278 
279 impl Polygon {
280     /// Parse the inner arguments of a `polygon` function.
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>281     fn parse_function_arguments<'i, 't>(
282         context: &ParserContext,
283         input: &mut Parser<'i, 't>,
284     ) -> Result<Self, ParseError<'i>> {
285         let fill = input
286             .try_parse(|i| -> Result<_, ParseError> {
287                 let fill = FillRule::parse(i)?;
288                 i.expect_comma()?; // only eat the comma if there is something before it
289                 Ok(fill)
290             })
291             .unwrap_or_default();
292 
293         let coordinates = input
294             .parse_comma_separated(|i| {
295                 Ok(PolygonCoord(
296                     LengthPercentage::parse(context, i)?,
297                     LengthPercentage::parse(context, i)?,
298                 ))
299             })?
300             .into();
301 
302         Ok(Polygon { fill, coordinates })
303     }
304 }
305 
306 impl Parse for Path {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>307     fn parse<'i, 't>(
308         context: &ParserContext,
309         input: &mut Parser<'i, 't>,
310     ) -> Result<Self, ParseError<'i>> {
311         input.expect_function_matching("path")?;
312         input.parse_nested_block(|i| Self::parse_function_arguments(context, i))
313     }
314 }
315 
316 impl Path {
317     /// Parse the inner arguments of a `path` function.
parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>318     fn parse_function_arguments<'i, 't>(
319         context: &ParserContext,
320         input: &mut Parser<'i, 't>,
321     ) -> Result<Self, ParseError<'i>> {
322         let fill = input
323             .try_parse(|i| -> Result<_, ParseError> {
324                 let fill = FillRule::parse(i)?;
325                 i.expect_comma()?;
326                 Ok(fill)
327             })
328             .unwrap_or_default();
329         let path = SVGPathData::parse(context, input)?;
330         Ok(Path { fill, path })
331     }
332 }
333