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