1use 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 pub struct BorderRadius(VendorPrefix) {
23 top_left: BorderTopLeftRadius(Size2D<LengthPercentage>, VendorPrefix),
25 top_right: BorderTopRightRadius(Size2D<LengthPercentage>, VendorPrefix),
27 bottom_right: BorderBottomRightRadius(Size2D<LengthPercentage>, VendorPrefix),
29 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 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 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 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}