lightningcss/properties/
border_radius.rs

1//! The CSS border radius property.
2
3use crate::compat;
4use crate::context::PropertyHandlerContext;
5use crate::declaration::{DeclarationBlock, DeclarationList};
6use crate::error::{ParserError, PrinterError};
7use crate::logical::PropertyCategory;
8use crate::macros::define_shorthand;
9use crate::prefixes::Feature;
10use crate::printer::Printer;
11use crate::properties::{Property, PropertyId, VendorPrefix};
12use crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss, Zero};
13use crate::values::length::*;
14use crate::values::rect::Rect;
15use crate::values::size::Size2D;
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use cssparser::*;
19
20define_shorthand! {
21  /// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property.
22  pub struct BorderRadius(VendorPrefix) {
23    /// The x and y radius values for the top left corner.
24    top_left: BorderTopLeftRadius(Size2D<LengthPercentage>, VendorPrefix),
25    /// The x and y radius values for the top right corner.
26    top_right: BorderTopRightRadius(Size2D<LengthPercentage>, VendorPrefix),
27    /// The x and y radius values for the bottom right corner.
28    bottom_right: BorderBottomRightRadius(Size2D<LengthPercentage>, VendorPrefix),
29    /// The x and y radius values for the bottom left corner.
30    bottom_left: BorderBottomLeftRadius(Size2D<LengthPercentage>, VendorPrefix),
31  }
32}
33
34impl Default for BorderRadius {
35  fn default() -> BorderRadius {
36    let zero = Size2D(LengthPercentage::zero(), LengthPercentage::zero());
37    BorderRadius {
38      top_left: zero.clone(),
39      top_right: zero.clone(),
40      bottom_right: zero.clone(),
41      bottom_left: zero,
42    }
43  }
44}
45
46impl<'i> Parse<'i> for BorderRadius {
47  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
48    let widths: Rect<LengthPercentage> = Rect::parse(input)?;
49    let heights = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
50      Rect::parse(input)?
51    } else {
52      widths.clone()
53    };
54
55    Ok(BorderRadius {
56      top_left: Size2D(widths.0, heights.0),
57      top_right: Size2D(widths.1, heights.1),
58      bottom_right: Size2D(widths.2, heights.2),
59      bottom_left: Size2D(widths.3, heights.3),
60    })
61  }
62}
63
64impl ToCss for BorderRadius {
65  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
66  where
67    W: std::fmt::Write,
68  {
69    let widths = Rect::new(
70      &self.top_left.0,
71      &self.top_right.0,
72      &self.bottom_right.0,
73      &self.bottom_left.0,
74    );
75    let heights = Rect::new(
76      &self.top_left.1,
77      &self.top_right.1,
78      &self.bottom_right.1,
79      &self.bottom_left.1,
80    );
81
82    widths.to_css(dest)?;
83    if widths != heights {
84      dest.delim('/', true)?;
85      heights.to_css(dest)?;
86    }
87
88    Ok(())
89  }
90}
91
92#[derive(Default, Debug)]
93pub(crate) struct BorderRadiusHandler<'i> {
94  top_left: Option<(Size2D<LengthPercentage>, VendorPrefix)>,
95  top_right: Option<(Size2D<LengthPercentage>, VendorPrefix)>,
96  bottom_right: Option<(Size2D<LengthPercentage>, VendorPrefix)>,
97  bottom_left: Option<(Size2D<LengthPercentage>, VendorPrefix)>,
98  start_start: Option<Property<'i>>,
99  start_end: Option<Property<'i>>,
100  end_end: Option<Property<'i>>,
101  end_start: Option<Property<'i>>,
102  category: PropertyCategory,
103  has_any: bool,
104}
105
106impl<'i> PropertyHandler<'i> for BorderRadiusHandler<'i> {
107  fn handle_property(
108    &mut self,
109    property: &Property<'i>,
110    dest: &mut DeclarationList<'i>,
111    context: &mut PropertyHandlerContext<'i, '_>,
112  ) -> bool {
113    use Property::*;
114
115    macro_rules! maybe_flush {
116      ($prop: ident, $val: expr, $vp: expr) => {{
117        // If two vendor prefixes for the same property have different
118        // values, we need to flush what we have immediately to preserve order.
119        if let Some((val, prefixes)) = &self.$prop {
120          if val != $val && !prefixes.contains(*$vp) {
121            self.flush(dest, context);
122          }
123        }
124
125        if self.$prop.is_some() && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
126          self.flush(dest, context);
127        }
128      }};
129    }
130
131    macro_rules! property {
132      ($prop: ident, $val: expr, $vp: ident) => {{
133        if self.category != PropertyCategory::Physical {
134          self.flush(dest, context);
135        }
136
137        maybe_flush!($prop, $val, $vp);
138
139        // Otherwise, update the value and add the prefix.
140        if let Some((val, prefixes)) = &mut self.$prop {
141          *val = $val.clone();
142          *prefixes |= *$vp;
143        } else {
144          self.$prop = Some(($val.clone(), *$vp));
145          self.has_any = true;
146        }
147
148        self.category = PropertyCategory::Physical;
149      }};
150    }
151
152    macro_rules! logical_property {
153      ($prop: ident) => {{
154        if self.category != PropertyCategory::Logical {
155          self.flush(dest, context);
156        }
157
158        self.$prop = Some(property.clone());
159        self.category = PropertyCategory::Logical;
160        self.has_any = true;
161      }};
162    }
163
164    match property {
165      BorderTopLeftRadius(val, vp) => property!(top_left, val, vp),
166      BorderTopRightRadius(val, vp) => property!(top_right, val, vp),
167      BorderBottomRightRadius(val, vp) => property!(bottom_right, val, vp),
168      BorderBottomLeftRadius(val, vp) => property!(bottom_left, val, vp),
169      BorderStartStartRadius(_) => logical_property!(start_start),
170      BorderStartEndRadius(_) => logical_property!(start_end),
171      BorderEndEndRadius(_) => logical_property!(end_end),
172      BorderEndStartRadius(_) => logical_property!(end_start),
173      BorderRadius(val, vp) => {
174        self.start_start = None;
175        self.start_end = None;
176        self.end_end = None;
177        self.end_start = None;
178        maybe_flush!(top_left, &val.top_left, vp);
179        maybe_flush!(top_right, &val.top_right, vp);
180        maybe_flush!(bottom_right, &val.bottom_right, vp);
181        maybe_flush!(bottom_left, &val.bottom_left, vp);
182        property!(top_left, &val.top_left, vp);
183        property!(top_right, &val.top_right, vp);
184        property!(bottom_right, &val.bottom_right, vp);
185        property!(bottom_left, &val.bottom_left, vp);
186      }
187      Unparsed(val) if is_border_radius_property(&val.property_id) => {
188        // Even if we weren't able to parse the value (e.g. due to var() references),
189        // we can still add vendor prefixes to the property itself.
190        match &val.property_id {
191          PropertyId::BorderStartStartRadius => logical_property!(start_start),
192          PropertyId::BorderStartEndRadius => logical_property!(start_end),
193          PropertyId::BorderEndEndRadius => logical_property!(end_end),
194          PropertyId::BorderEndStartRadius => logical_property!(end_start),
195          _ => {
196            self.flush(dest, context);
197            dest.push(Property::Unparsed(
198              val.get_prefixed(context.targets, Feature::BorderRadius),
199            ));
200          }
201        }
202      }
203      _ => return false,
204    }
205
206    true
207  }
208
209  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
210    self.flush(dest, context);
211  }
212}
213
214impl<'i> BorderRadiusHandler<'i> {
215  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
216    if !self.has_any {
217      return;
218    }
219
220    self.has_any = false;
221
222    let mut top_left = std::mem::take(&mut self.top_left);
223    let mut top_right = std::mem::take(&mut self.top_right);
224    let mut bottom_right = std::mem::take(&mut self.bottom_right);
225    let mut bottom_left = std::mem::take(&mut self.bottom_left);
226    let start_start = std::mem::take(&mut self.start_start);
227    let start_end = std::mem::take(&mut self.start_end);
228    let end_end = std::mem::take(&mut self.end_end);
229    let end_start = std::mem::take(&mut self.end_start);
230
231    if let (
232      Some((top_left, tl_prefix)),
233      Some((top_right, tr_prefix)),
234      Some((bottom_right, br_prefix)),
235      Some((bottom_left, bl_prefix)),
236    ) = (&mut top_left, &mut top_right, &mut bottom_right, &mut bottom_left)
237    {
238      let intersection = *tl_prefix & *tr_prefix & *br_prefix & *bl_prefix;
239      if !intersection.is_empty() {
240        let prefix = context.targets.prefixes(intersection, Feature::BorderRadius);
241        dest.push(Property::BorderRadius(
242          BorderRadius {
243            top_left: top_left.clone(),
244            top_right: top_right.clone(),
245            bottom_right: bottom_right.clone(),
246            bottom_left: bottom_left.clone(),
247          },
248          prefix,
249        ));
250        tl_prefix.remove(intersection);
251        tr_prefix.remove(intersection);
252        br_prefix.remove(intersection);
253        bl_prefix.remove(intersection);
254      }
255    }
256
257    macro_rules! single_property {
258      ($prop: ident, $key: ident) => {
259        if let Some((val, mut vp)) = $key {
260          if !vp.is_empty() {
261            vp = context.targets.prefixes(vp, Feature::$prop);
262            dest.push(Property::$prop(val, vp))
263          }
264        }
265      };
266    }
267
268    let logical_supported = !context.should_compile_logical(compat::Feature::LogicalBorderRadius);
269
270    macro_rules! logical_property {
271      ($prop: ident, $key: ident, $ltr: ident, $rtl: ident) => {
272        if let Some(val) = $key {
273          if logical_supported {
274            dest.push(val);
275          } else {
276            let vp = context.targets.prefixes(VendorPrefix::None, Feature::$ltr);
277            match val {
278              Property::BorderStartStartRadius(val)
279              | Property::BorderStartEndRadius(val)
280              | Property::BorderEndEndRadius(val)
281              | Property::BorderEndStartRadius(val) => {
282                context.add_logical_rule(Property::$ltr(val.clone(), vp), Property::$rtl(val, vp));
283              }
284              Property::Unparsed(val) => {
285                context.add_logical_rule(
286                  Property::Unparsed(val.with_property_id(PropertyId::$ltr(vp))),
287                  Property::Unparsed(val.with_property_id(PropertyId::$rtl(vp))),
288                );
289              }
290              _ => {}
291            }
292          }
293        }
294      };
295    }
296
297    single_property!(BorderTopLeftRadius, top_left);
298    single_property!(BorderTopRightRadius, top_right);
299    single_property!(BorderBottomRightRadius, bottom_right);
300    single_property!(BorderBottomLeftRadius, bottom_left);
301    logical_property!(
302      BorderStartStartRadius,
303      start_start,
304      BorderTopLeftRadius,
305      BorderTopRightRadius
306    );
307    logical_property!(
308      BorderStartEndRadius,
309      start_end,
310      BorderTopRightRadius,
311      BorderTopLeftRadius
312    );
313    logical_property!(
314      BorderEndEndRadius,
315      end_end,
316      BorderBottomRightRadius,
317      BorderBottomLeftRadius
318    );
319    logical_property!(
320      BorderEndStartRadius,
321      end_start,
322      BorderBottomLeftRadius,
323      BorderBottomRightRadius
324    );
325  }
326}
327
328#[inline]
329fn is_border_radius_property(property_id: &PropertyId) -> bool {
330  if is_logical_border_radius_property(property_id) {
331    return true;
332  }
333
334  match property_id {
335    PropertyId::BorderTopLeftRadius(_)
336    | PropertyId::BorderTopRightRadius(_)
337    | PropertyId::BorderBottomRightRadius(_)
338    | PropertyId::BorderBottomLeftRadius(_)
339    | PropertyId::BorderRadius(_) => true,
340    _ => false,
341  }
342}
343
344#[inline]
345fn is_logical_border_radius_property(property_id: &PropertyId) -> bool {
346  match property_id {
347    PropertyId::BorderStartStartRadius
348    | PropertyId::BorderStartEndRadius
349    | PropertyId::BorderEndEndRadius
350    | PropertyId::BorderEndStartRadius => true,
351    _ => false,
352  }
353}