use crate::error::{ParserError, PrinterError};
use crate::printer::Printer;
use crate::targets::{Browsers, Targets};
use crate::traits::{FallbackValues, IsCompatible, Parse, ToCss, Zero};
use crate::values::color::ColorFallbackKind;
use crate::values::{angle::Angle, color::CssColor, length::Length, percentage::NumberOrPercentage, url::Url};
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Filter<'i> {
Blur(Length),
Brightness(NumberOrPercentage),
Contrast(NumberOrPercentage),
Grayscale(NumberOrPercentage),
HueRotate(Angle),
Invert(NumberOrPercentage),
Opacity(NumberOrPercentage),
Saturate(NumberOrPercentage),
Sepia(NumberOrPercentage),
DropShadow(DropShadow),
#[cfg_attr(feature = "serde", serde(borrow))]
Url(Url<'i>),
}
impl<'i> Parse<'i> for Filter<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(url) = input.try_parse(Url::parse) {
return Ok(Filter::Url(url));
}
let location = input.current_source_location();
let function = input.expect_function()?;
match_ignore_ascii_case! { &function,
"blur" => {
input.parse_nested_block(|input| {
Ok(Filter::Blur(input.try_parse(Length::parse).unwrap_or(Length::zero())))
})
},
"brightness" => {
input.parse_nested_block(|input| {
Ok(Filter::Brightness(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"contrast" => {
input.parse_nested_block(|input| {
Ok(Filter::Contrast(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"grayscale" => {
input.parse_nested_block(|input| {
Ok(Filter::Grayscale(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"hue-rotate" => {
input.parse_nested_block(|input| {
Ok(Filter::HueRotate(input.try_parse(Angle::parse_with_unitless_zero).unwrap_or(Angle::zero())))
})
},
"invert" => {
input.parse_nested_block(|input| {
Ok(Filter::Invert(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"opacity" => {
input.parse_nested_block(|input| {
Ok(Filter::Opacity(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"saturate" => {
input.parse_nested_block(|input| {
Ok(Filter::Saturate(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"sepia" => {
input.parse_nested_block(|input| {
Ok(Filter::Sepia(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
})
},
"drop-shadow" => {
input.parse_nested_block(|input| {
Ok(Filter::DropShadow(DropShadow::parse(input)?))
})
},
_ => Err(location.new_unexpected_token_error(
cssparser::Token::Ident(function.clone())
))
}
}
}
impl<'i> ToCss for Filter<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
Filter::Blur(val) => {
dest.write_str("blur(")?;
if *val != Length::zero() {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Brightness(val) => {
dest.write_str("brightness(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Contrast(val) => {
dest.write_str("contrast(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Grayscale(val) => {
dest.write_str("grayscale(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::HueRotate(val) => {
dest.write_str("hue-rotate(")?;
if !val.is_zero() {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Invert(val) => {
dest.write_str("invert(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Opacity(val) => {
dest.write_str("opacity(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Saturate(val) => {
dest.write_str("saturate(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::Sepia(val) => {
dest.write_str("sepia(")?;
let v: f32 = val.into();
if v != 1.0 {
val.to_css(dest)?;
}
dest.write_char(')')
}
Filter::DropShadow(val) => {
dest.write_str("drop-shadow(")?;
val.to_css(dest)?;
dest.write_char(')')
}
Filter::Url(url) => url.to_css(dest),
}
}
}
impl<'i> Filter<'i> {
fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
match self {
Filter::DropShadow(shadow) => Filter::DropShadow(shadow.get_fallback(kind)),
_ => self.clone(),
}
}
}
impl IsCompatible for Filter<'_> {
fn is_compatible(&self, _browsers: Browsers) -> bool {
true
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct DropShadow {
pub color: CssColor,
pub x_offset: Length,
pub y_offset: Length,
pub blur: Length,
}
impl<'i> Parse<'i> for DropShadow {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut color = None;
let mut lengths = None;
loop {
if lengths.is_none() {
let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {
let horizontal = Length::parse(input)?;
let vertical = Length::parse(input)?;
let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());
Ok((horizontal, vertical, blur))
});
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(DropShadow {
color: color.unwrap_or(CssColor::current_color()),
x_offset: lengths.0,
y_offset: lengths.1,
blur: lengths.2,
})
}
}
impl ToCss for DropShadow {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.x_offset.to_css(dest)?;
dest.write_char(' ')?;
self.y_offset.to_css(dest)?;
if self.blur != Length::zero() {
dest.write_char(' ')?;
self.blur.to_css(dest)?;
}
if self.color != CssColor::current_color() {
dest.write_char(' ')?;
self.color.to_css(dest)?;
}
Ok(())
}
}
impl DropShadow {
fn get_fallback(&self, kind: ColorFallbackKind) -> DropShadow {
DropShadow {
color: self.color.get_fallback(kind),
..self.clone()
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum FilterList<'i> {
None,
#[cfg_attr(feature = "serde", serde(borrow))]
Filters(SmallVec<[Filter<'i>; 1]>),
}
impl<'i> Parse<'i> for FilterList<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
return Ok(FilterList::None);
}
let mut filters = SmallVec::new();
while let Ok(filter) = input.try_parse(Filter::parse) {
filters.push(filter);
}
Ok(FilterList::Filters(filters))
}
}
impl<'i> ToCss for FilterList<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
FilterList::None => dest.write_str("none"),
FilterList::Filters(filters) => {
let mut first = true;
for filter in filters {
if first {
first = false;
} else {
dest.whitespace()?;
}
filter.to_css(dest)?;
}
Ok(())
}
}
}
}
impl<'i> FallbackValues for FilterList<'i> {
fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
let mut res = Vec::new();
let mut fallbacks = ColorFallbackKind::empty();
if let FilterList::Filters(filters) = self {
for shadow in filters.iter() {
if let Filter::DropShadow(shadow) = &shadow {
fallbacks |= shadow.color.get_necessary_fallbacks(targets);
}
}
if fallbacks.contains(ColorFallbackKind::RGB) {
res.push(FilterList::Filters(
filters
.iter()
.map(|filter| filter.get_fallback(ColorFallbackKind::RGB))
.collect(),
));
}
if fallbacks.contains(ColorFallbackKind::P3) {
res.push(FilterList::Filters(
filters
.iter()
.map(|filter| filter.get_fallback(ColorFallbackKind::P3))
.collect(),
));
}
if fallbacks.contains(ColorFallbackKind::LAB) {
for filter in filters.iter_mut() {
*filter = filter.get_fallback(ColorFallbackKind::LAB);
}
}
}
res
}
}
impl IsCompatible for FilterList<'_> {
fn is_compatible(&self, _browsers: Browsers) -> bool {
true
}
}