lightningcss/properties/
box_shadow.rs

1//! The CSS box-shadow property.
2
3use super::PropertyId;
4use crate::context::PropertyHandlerContext;
5use crate::declaration::DeclarationList;
6use crate::error::{ParserError, PrinterError};
7use crate::prefixes::Feature;
8use crate::printer::Printer;
9use crate::properties::Property;
10use crate::targets::Browsers;
11use crate::traits::{IsCompatible, Parse, PropertyHandler, ToCss, Zero};
12use crate::values::color::{ColorFallbackKind, CssColor};
13use crate::values::length::Length;
14use crate::vendor_prefix::VendorPrefix;
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18use smallvec::SmallVec;
19
20/// A value for the [box-shadow](https://drafts.csswg.org/css-backgrounds/#box-shadow) property.
21#[derive(Debug, Clone, PartialEq)]
22#[cfg_attr(feature = "visitor", derive(Visit))]
23#[cfg_attr(
24  feature = "serde",
25  derive(serde::Serialize, serde::Deserialize),
26  serde(rename_all = "camelCase")
27)]
28#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
29#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
30pub struct BoxShadow {
31  /// The color of the box shadow.
32  pub color: CssColor,
33  /// The x offset of the shadow.
34  pub x_offset: Length,
35  /// The y offset of the shadow.
36  pub y_offset: Length,
37  /// The blur radius of the shadow.
38  pub blur: Length,
39  /// The spread distance of the shadow.
40  pub spread: Length,
41  /// Whether the shadow is inset within the box.
42  pub inset: bool,
43}
44
45impl<'i> Parse<'i> for BoxShadow {
46  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
47    let mut color = None;
48    let mut lengths = None;
49    let mut inset = false;
50
51    loop {
52      if !inset {
53        if input.try_parse(|input| input.expect_ident_matching("inset")).is_ok() {
54          inset = true;
55          continue;
56        }
57      }
58
59      if lengths.is_none() {
60        let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {
61          let horizontal = Length::parse(input)?;
62          let vertical = Length::parse(input)?;
63          let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());
64          let spread = input.try_parse(Length::parse).unwrap_or(Length::zero());
65          Ok((horizontal, vertical, blur, spread))
66        });
67
68        if let Ok(value) = value {
69          lengths = Some(value);
70          continue;
71        }
72      }
73
74      if color.is_none() {
75        if let Ok(value) = input.try_parse(CssColor::parse) {
76          color = Some(value);
77          continue;
78        }
79      }
80
81      break;
82    }
83
84    let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
85    Ok(BoxShadow {
86      color: color.unwrap_or(CssColor::current_color()),
87      x_offset: lengths.0,
88      y_offset: lengths.1,
89      blur: lengths.2,
90      spread: lengths.3,
91      inset,
92    })
93  }
94}
95
96impl ToCss for BoxShadow {
97  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
98  where
99    W: std::fmt::Write,
100  {
101    if self.inset {
102      dest.write_str("inset ")?;
103    }
104
105    self.x_offset.to_css(dest)?;
106    dest.write_char(' ')?;
107    self.y_offset.to_css(dest)?;
108
109    if self.blur != Length::zero() || self.spread != Length::zero() {
110      dest.write_char(' ')?;
111      self.blur.to_css(dest)?;
112
113      if self.spread != Length::zero() {
114        dest.write_char(' ')?;
115        self.spread.to_css(dest)?;
116      }
117    }
118
119    if self.color != CssColor::current_color() {
120      dest.write_char(' ')?;
121      self.color.to_css(dest)?;
122    }
123
124    Ok(())
125  }
126}
127
128impl IsCompatible for BoxShadow {
129  fn is_compatible(&self, browsers: Browsers) -> bool {
130    self.color.is_compatible(browsers)
131      && self.x_offset.is_compatible(browsers)
132      && self.y_offset.is_compatible(browsers)
133      && self.blur.is_compatible(browsers)
134      && self.spread.is_compatible(browsers)
135  }
136}
137
138#[derive(Default)]
139pub(crate) struct BoxShadowHandler {
140  box_shadows: Option<(SmallVec<[BoxShadow; 1]>, VendorPrefix)>,
141  flushed: bool,
142}
143
144impl<'i> PropertyHandler<'i> for BoxShadowHandler {
145  fn handle_property(
146    &mut self,
147    property: &Property<'i>,
148    dest: &mut DeclarationList<'i>,
149    context: &mut PropertyHandlerContext<'i, '_>,
150  ) -> bool {
151    match property {
152      Property::BoxShadow(box_shadows, prefix) => {
153        if self.box_shadows.is_some()
154          && matches!(context.targets.browsers, Some(browsers) if !box_shadows.is_compatible(browsers))
155        {
156          self.flush(dest, context);
157        }
158
159        if let Some((val, prefixes)) = &mut self.box_shadows {
160          if val != box_shadows && !prefixes.contains(*prefix) {
161            self.flush(dest, context);
162            self.box_shadows = Some((box_shadows.clone(), *prefix));
163          } else {
164            *val = box_shadows.clone();
165            *prefixes |= *prefix;
166          }
167        } else {
168          self.box_shadows = Some((box_shadows.clone(), *prefix));
169        }
170      }
171      Property::Unparsed(unparsed) if matches!(unparsed.property_id, PropertyId::BoxShadow(_)) => {
172        self.flush(dest, context);
173
174        let mut unparsed = unparsed.clone();
175        context.add_unparsed_fallbacks(&mut unparsed);
176        dest.push(Property::Unparsed(unparsed));
177        self.flushed = true;
178      }
179      _ => return false,
180    }
181
182    true
183  }
184
185  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
186    self.flush(dest, context);
187    self.flushed = false;
188  }
189}
190
191impl BoxShadowHandler {
192  fn flush<'i>(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
193    if self.box_shadows.is_none() {
194      return;
195    }
196
197    let box_shadows = std::mem::take(&mut self.box_shadows);
198
199    if let Some((box_shadows, prefixes)) = box_shadows {
200      if !self.flushed {
201        let mut prefixes = context.targets.prefixes(prefixes, Feature::BoxShadow);
202        let mut fallbacks = ColorFallbackKind::empty();
203        for shadow in &box_shadows {
204          fallbacks |= shadow.color.get_necessary_fallbacks(context.targets);
205        }
206
207        if fallbacks.contains(ColorFallbackKind::RGB) {
208          let rgb = box_shadows
209            .iter()
210            .map(|shadow| BoxShadow {
211              color: shadow.color.to_rgb().unwrap_or_else(|_| shadow.color.clone()),
212              ..shadow.clone()
213            })
214            .collect();
215          dest.push(Property::BoxShadow(rgb, prefixes));
216          if prefixes.contains(VendorPrefix::None) {
217            prefixes = VendorPrefix::None;
218          } else {
219            // Only output RGB for prefixed property (e.g. -webkit-box-shadow)
220            return;
221          }
222        }
223
224        if fallbacks.contains(ColorFallbackKind::P3) {
225          let p3 = box_shadows
226            .iter()
227            .map(|shadow| BoxShadow {
228              color: shadow.color.to_p3().unwrap_or_else(|_| shadow.color.clone()),
229              ..shadow.clone()
230            })
231            .collect();
232          dest.push(Property::BoxShadow(p3, VendorPrefix::None));
233        }
234
235        if fallbacks.contains(ColorFallbackKind::LAB) {
236          let lab = box_shadows
237            .iter()
238            .map(|shadow| BoxShadow {
239              color: shadow.color.to_lab().unwrap_or_else(|_| shadow.color.clone()),
240              ..shadow.clone()
241            })
242            .collect();
243          dest.push(Property::BoxShadow(lab, VendorPrefix::None));
244        } else {
245          dest.push(Property::BoxShadow(box_shadows, prefixes))
246        }
247      } else {
248        dest.push(Property::BoxShadow(box_shadows, prefixes))
249      }
250    }
251
252    self.flushed = true;
253  }
254}