//! The CSS box-shadow property. use super::PropertyId; use crate::context::PropertyHandlerContext; use crate::declaration::DeclarationList; use crate::error::{ParserError, PrinterError}; use crate::prefixes::Feature; use crate::printer::Printer; use crate::properties::Property; use crate::targets::Browsers; use crate::traits::{Parse, PropertyHandler, ToCss, Zero}; use crate::values::color::{ColorFallbackKind, CssColor}; use crate::values::length::Length; use crate::vendor_prefix::VendorPrefix; use cssparser::*; use smallvec::SmallVec; /// A value for the [box-shadow](https://drafts.csswg.org/css-backgrounds/#box-shadow) property. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BoxShadow { /// The color of the box shadow. pub color: CssColor, /// The x offset of the shadow. pub x_offset: Length, /// The y offset of the shadow. pub y_offset: Length, /// The blur radius of the shadow. pub blur: Length, /// The spread distance of the shadow. pub spread: Length, /// Whether the shadow is inset within the box. pub inset: bool, } impl<'i> Parse<'i> for BoxShadow { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let mut color = None; let mut lengths = None; let mut inset = false; loop { if !inset { if input.try_parse(|input| input.expect_ident_matching("inset")).is_ok() { inset = true; continue; } } if lengths.is_none() { let value = input.try_parse::<_, _, ParseError>>(|input| { let horizontal = Length::parse(input)?; let vertical = Length::parse(input)?; let blur = input.try_parse(Length::parse).unwrap_or(Length::zero()); let spread = input.try_parse(Length::parse).unwrap_or(Length::zero()); Ok((horizontal, vertical, blur, spread)) }); if let Ok(value) = value { lengths = Some(value); continue; } } if color.is_none() { if let Ok(value) = input.try_parse(CssColor::parse) { color = Some(value); continue; } } break; } let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?; Ok(BoxShadow { color: color.unwrap_or(CssColor::current_color()), x_offset: lengths.0, y_offset: lengths.1, blur: lengths.2, spread: lengths.3, inset, }) } } impl ToCss for BoxShadow { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { if self.inset { dest.write_str("inset ")?; } self.x_offset.to_css(dest)?; dest.write_char(' ')?; self.y_offset.to_css(dest)?; if self.blur != Length::zero() || self.spread != Length::zero() { dest.write_char(' ')?; self.blur.to_css(dest)?; if self.spread != Length::zero() { dest.write_char(' ')?; self.spread.to_css(dest)?; } } if self.color != CssColor::current_color() { dest.write_char(' ')?; self.color.to_css(dest)?; } Ok(()) } } #[derive(Default)] pub(crate) struct BoxShadowHandler { targets: Option, box_shadows: Option<(SmallVec<[BoxShadow; 1]>, VendorPrefix)>, } impl BoxShadowHandler { pub fn new(targets: Option) -> BoxShadowHandler { BoxShadowHandler { targets, ..BoxShadowHandler::default() } } } impl<'i> PropertyHandler<'i> for BoxShadowHandler { fn handle_property( &mut self, property: &Property<'i>, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { match property { Property::BoxShadow(box_shadows, prefix) => { if let Some((val, prefixes)) = &mut self.box_shadows { if val != box_shadows && !prefixes.contains(*prefix) { self.finalize(dest, context); self.box_shadows = Some((box_shadows.clone(), *prefix)); } else { *val = box_shadows.clone(); *prefixes |= *prefix; } } else { self.box_shadows = Some((box_shadows.clone(), *prefix)); } } Property::Unparsed(unparsed) if matches!(unparsed.property_id, PropertyId::BoxShadow(_)) => { self.finalize(dest, context); let mut unparsed = unparsed.clone(); context.add_unparsed_fallbacks(&mut unparsed); dest.push(Property::Unparsed(unparsed)) } _ => return false, } true } fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) { if self.box_shadows.is_none() { return; } let box_shadows = std::mem::take(&mut self.box_shadows); if let Some((box_shadows, prefixes)) = box_shadows { if let Some(targets) = self.targets { let mut prefixes = if prefixes.contains(VendorPrefix::None) { Feature::BoxShadow.prefixes_for(targets) } else { prefixes }; let mut fallbacks = ColorFallbackKind::empty(); for shadow in &box_shadows { fallbacks |= shadow.color.get_necessary_fallbacks(targets); } if fallbacks.contains(ColorFallbackKind::RGB) { let rgb = box_shadows .iter() .map(|shadow| BoxShadow { color: shadow.color.to_rgb(), ..shadow.clone() }) .collect(); dest.push(Property::BoxShadow(rgb, prefixes)); if prefixes.contains(VendorPrefix::None) { prefixes = VendorPrefix::None; } else { // Only output RGB for prefixed property (e.g. -webkit-box-shadow) return; } } if fallbacks.contains(ColorFallbackKind::P3) { let p3 = box_shadows .iter() .map(|shadow| BoxShadow { color: shadow.color.to_p3(), ..shadow.clone() }) .collect(); dest.push(Property::BoxShadow(p3, VendorPrefix::None)); } if fallbacks.contains(ColorFallbackKind::LAB) { let lab = box_shadows .iter() .map(|shadow| BoxShadow { color: shadow.color.to_lab(), ..shadow.clone() }) .collect(); dest.push(Property::BoxShadow(lab, VendorPrefix::None)); } else { dest.push(Property::BoxShadow(box_shadows, prefixes)) } } else { dest.push(Property::BoxShadow(box_shadows, prefixes)) } } } }