lightningcss/properties/
box_shadow.rs1use 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#[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 pub color: CssColor,
33 pub x_offset: Length,
35 pub y_offset: Length,
37 pub blur: Length,
39 pub spread: Length,
41 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 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}