lightningcss/values/
shape.rs

1//! CSS shape values for masking and clipping.
2
3use 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/// A CSS [`<basic-shape>`](https://www.w3.org/TR/css-shapes-1/#basic-shape-functions) value.
16#[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  /// An inset rectangle.
27  Inset(InsetRect),
28  /// A circle.
29  Circle(Circle),
30  /// An ellipse.
31  Ellipse(Ellipse),
32  /// A polygon.
33  Polygon(Polygon),
34}
35
36/// An [`inset()`](https://www.w3.org/TR/css-shapes-1/#funcdef-inset) rectangle shape.
37#[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  /// The rectangle.
43  pub rect: Rect<LengthPercentage>,
44  /// A corner radius for the rectangle.
45  pub radius: BorderRadius,
46}
47
48/// A [`circle()`](https://www.w3.org/TR/css-shapes-1/#funcdef-circle) shape.
49#[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  /// The radius of the circle.
55  pub radius: ShapeRadius,
56  /// The position of the center of the circle.
57  pub position: Position,
58}
59
60/// A [`<shape-radius>`](https://www.w3.org/TR/css-shapes-1/#typedef-shape-radius) value
61/// that defines the radius of a `circle()` or `ellipse()` shape.
62#[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  /// An explicit length or percentage.
72  LengthPercentage(LengthPercentage),
73  /// The length from the center to the closest side of the box.
74  ClosestSide,
75  /// The length from the center to the farthest side of the box.
76  FarthestSide,
77}
78
79/// An [`ellipse()`](https://www.w3.org/TR/css-shapes-1/#funcdef-ellipse) shape.
80#[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  /// The x-radius of the ellipse.
90  pub radius_x: ShapeRadius,
91  /// The y-radius of the ellipse.
92  pub radius_y: ShapeRadius,
93  /// The position of the center of the ellipse.
94  pub position: Position,
95}
96
97/// A [`polygon()`](https://www.w3.org/TR/css-shapes-1/#funcdef-polygon) shape.
98#[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  /// The fill rule used to determine the interior of the polygon.
108  pub fill_rule: FillRule,
109  /// The points of each vertex of the polygon.
110  pub points: Vec<Point>,
111}
112
113/// A point within a `polygon()` shape.
114///
115/// See [Polygon](Polygon).
116#[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  /// The x position of the point.
122  x: LengthPercentage,
123  /// the y position of the point.
124  y: LengthPercentage,
125}
126
127enum_property! {
128  /// A [`<fill-rule>`](https://www.w3.org/TR/css-shapes-1/#typedef-fill-rule) used to
129  /// determine the interior of a `polygon()` shape.
130  ///
131  /// See [Polygon](Polygon).
132  pub enum FillRule {
133    /// The `nonzero` fill rule.
134    Nonzero,
135    /// The `evenodd` fill rule.
136    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}