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