1use super::length::LengthPercentage;
4use super::position::Position;
5use super::rect::Rect;
6use crate::error::{ParserError, PrinterError};
7use crate::macros::enum_property;
8use crate::printer::Printer;
9use crate::properties::border_radius::BorderRadius;
10use crate::traits::{Parse, ToCss};
11#[cfg(feature = "visitor")]
12use crate::visitor::Visit;
13use cssparser::*;
14
15#[derive(Debug, Clone, PartialEq)]
17#[cfg_attr(feature = "visitor", derive(Visit))]
18#[cfg_attr(
19 feature = "serde",
20 derive(serde::Serialize, serde::Deserialize),
21 serde(tag = "type", content = "value", rename_all = "kebab-case")
22)]
23#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
24#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
25pub enum BasicShape {
26 Inset(InsetRect),
28 Circle(Circle),
30 Ellipse(Ellipse),
32 Polygon(Polygon),
34}
35
36#[derive(Debug, Clone, PartialEq)]
38#[cfg_attr(feature = "visitor", derive(Visit))]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
41pub struct InsetRect {
42 pub rect: Rect<LengthPercentage>,
44 pub radius: BorderRadius,
46}
47
48#[derive(Debug, Clone, PartialEq)]
50#[cfg_attr(feature = "visitor", derive(Visit))]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
53pub struct Circle {
54 pub radius: ShapeRadius,
56 pub position: Position,
58}
59
60#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
63#[cfg_attr(feature = "visitor", derive(Visit))]
64#[cfg_attr(
65 feature = "serde",
66 derive(serde::Serialize, serde::Deserialize),
67 serde(tag = "type", content = "value", rename_all = "kebab-case")
68)]
69#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
70pub enum ShapeRadius {
71 LengthPercentage(LengthPercentage),
73 ClosestSide,
75 FarthestSide,
77}
78
79#[derive(Debug, Clone, PartialEq)]
81#[cfg_attr(feature = "visitor", derive(Visit))]
82#[cfg_attr(
83 feature = "serde",
84 derive(serde::Serialize, serde::Deserialize),
85 serde(rename_all = "camelCase")
86)]
87#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
88pub struct Ellipse {
89 pub radius_x: ShapeRadius,
91 pub radius_y: ShapeRadius,
93 pub position: Position,
95}
96
97#[derive(Debug, Clone, PartialEq)]
99#[cfg_attr(feature = "visitor", derive(Visit))]
100#[cfg_attr(
101 feature = "serde",
102 derive(serde::Serialize, serde::Deserialize),
103 serde(rename_all = "camelCase")
104)]
105#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
106pub struct Polygon {
107 pub fill_rule: FillRule,
109 pub points: Vec<Point>,
111}
112
113#[derive(Debug, Clone, PartialEq)]
117#[cfg_attr(feature = "visitor", derive(Visit))]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
120pub struct Point {
121 x: LengthPercentage,
123 y: LengthPercentage,
125}
126
127enum_property! {
128 pub enum FillRule {
133 Nonzero,
135 Evenodd,
137 }
138}
139
140impl Default for FillRule {
141 fn default() -> FillRule {
142 FillRule::Nonzero
143 }
144}
145
146impl<'i> Parse<'i> for BasicShape {
147 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
148 let location = input.current_source_location();
149 let f = input.expect_function()?;
150 match_ignore_ascii_case! { &f,
151 "inset" => Ok(BasicShape::Inset(input.parse_nested_block(InsetRect::parse)?)),
152 "circle" => Ok(BasicShape::Circle(input.parse_nested_block(Circle::parse)?)),
153 "ellipse" => Ok(BasicShape::Ellipse(input.parse_nested_block(Ellipse::parse)?)),
154 "polygon" => Ok(BasicShape::Polygon(input.parse_nested_block(Polygon::parse)?)),
155 _ => Err(location.new_unexpected_token_error(Token::Ident(f.clone()))),
156 }
157 }
158}
159
160impl<'i> Parse<'i> for InsetRect {
161 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
162 let rect = Rect::parse(input)?;
163 let radius = if input.try_parse(|input| input.expect_ident_matching("round")).is_ok() {
164 BorderRadius::parse(input)?
165 } else {
166 BorderRadius::default()
167 };
168 Ok(InsetRect { rect, radius })
169 }
170}
171
172impl<'i> Parse<'i> for Circle {
173 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
174 let radius = input.try_parse(ShapeRadius::parse).unwrap_or_default();
175 let position = if input.try_parse(|input| input.expect_ident_matching("at")).is_ok() {
176 Position::parse(input)?
177 } else {
178 Position::center()
179 };
180
181 Ok(Circle { radius, position })
182 }
183}
184
185impl Default for ShapeRadius {
186 fn default() -> ShapeRadius {
187 ShapeRadius::ClosestSide
188 }
189}
190
191impl<'i> Parse<'i> for Ellipse {
192 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
193 let (x, y) = input
194 .try_parse(|input| -> Result<_, ParseError<'i, ParserError<'i>>> {
195 Ok((ShapeRadius::parse(input)?, ShapeRadius::parse(input)?))
196 })
197 .unwrap_or_default();
198
199 let position = if input.try_parse(|input| input.expect_ident_matching("at")).is_ok() {
200 Position::parse(input)?
201 } else {
202 Position::center()
203 };
204
205 Ok(Ellipse {
206 radius_x: x,
207 radius_y: y,
208 position,
209 })
210 }
211}
212
213impl<'i> Parse<'i> for Polygon {
214 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
215 let fill_rule = input.try_parse(FillRule::parse);
216 if fill_rule.is_ok() {
217 input.expect_comma()?;
218 }
219
220 let points = input.parse_comma_separated(Point::parse)?;
221 Ok(Polygon {
222 fill_rule: fill_rule.unwrap_or_default(),
223 points,
224 })
225 }
226}
227
228impl<'i> Parse<'i> for Point {
229 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
230 let x = LengthPercentage::parse(input)?;
231 let y = LengthPercentage::parse(input)?;
232 Ok(Point { x, y })
233 }
234}
235
236impl ToCss for BasicShape {
237 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
238 where
239 W: std::fmt::Write,
240 {
241 match self {
242 BasicShape::Inset(rect) => {
243 dest.write_str("inset(")?;
244 rect.to_css(dest)?;
245 dest.write_char(')')
246 }
247 BasicShape::Circle(circle) => {
248 dest.write_str("circle(")?;
249 circle.to_css(dest)?;
250 dest.write_char(')')
251 }
252 BasicShape::Ellipse(ellipse) => {
253 dest.write_str("ellipse(")?;
254 ellipse.to_css(dest)?;
255 dest.write_char(')')
256 }
257 BasicShape::Polygon(poly) => {
258 dest.write_str("polygon(")?;
259 poly.to_css(dest)?;
260 dest.write_char(')')
261 }
262 }
263 }
264}
265
266impl ToCss for InsetRect {
267 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
268 where
269 W: std::fmt::Write,
270 {
271 self.rect.to_css(dest)?;
272 if self.radius != BorderRadius::default() {
273 dest.write_str(" round ")?;
274 self.radius.to_css(dest)?;
275 }
276 Ok(())
277 }
278}
279
280impl ToCss for Circle {
281 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
282 where
283 W: std::fmt::Write,
284 {
285 let mut has_output = false;
286 if self.radius != ShapeRadius::default() {
287 self.radius.to_css(dest)?;
288 has_output = true;
289 }
290
291 if !self.position.is_center() {
292 if has_output {
293 dest.write_char(' ')?;
294 }
295 dest.write_str("at ")?;
296 self.position.to_css(dest)?;
297 }
298
299 Ok(())
300 }
301}
302
303impl ToCss for Ellipse {
304 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
305 where
306 W: std::fmt::Write,
307 {
308 let mut has_output = false;
309 if self.radius_x != ShapeRadius::default() || self.radius_y != ShapeRadius::default() {
310 self.radius_x.to_css(dest)?;
311 dest.write_char(' ')?;
312 self.radius_y.to_css(dest)?;
313 has_output = true;
314 }
315
316 if !self.position.is_center() {
317 if has_output {
318 dest.write_char(' ')?;
319 }
320 dest.write_str("at ")?;
321 self.position.to_css(dest)?;
322 }
323
324 Ok(())
325 }
326}
327
328impl ToCss for Polygon {
329 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
330 where
331 W: std::fmt::Write,
332 {
333 if self.fill_rule != FillRule::default() {
334 self.fill_rule.to_css(dest)?;
335 dest.delim(',', false)?;
336 }
337
338 let mut first = true;
339 for point in &self.points {
340 if first {
341 first = false;
342 } else {
343 dest.delim(',', false)?;
344 }
345 point.to_css(dest)?;
346 }
347
348 Ok(())
349 }
350}
351
352impl ToCss for Point {
353 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
354 where
355 W: std::fmt::Write,
356 {
357 self.x.to_css(dest)?;
358 dest.write_char(' ')?;
359 self.y.to_css(dest)
360 }
361}