use crate::compat;
use crate::error::{ParserError, PrinterError, PrinterErrorKind};
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::properties::PropertyId;
use crate::rules::supports::SupportsCondition;
use crate::stylesheet::ParserOptions;
use crate::targets::Browsers;
use crate::traits::{Parse, ParseWithOptions, ToCss};
use crate::values::angle::Angle;
use crate::values::color::{
parse_hsl_hwb_components, parse_rgb_components, ColorFallbackKind, ComponentParser, CssColor,
};
use crate::values::ident::{DashedIdentReference, Ident};
use crate::values::length::{serialize_dimension, LengthValue};
use crate::values::percentage::Percentage;
use crate::values::resolution::Resolution;
use crate::values::string::CowArcStr;
use crate::values::time::Time;
use crate::values::url::Url;
use crate::vendor_prefix::VendorPrefix;
use crate::visitor::Visit;
use cssparser::*;
#[derive(Debug, Clone, PartialEq, Visit)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CustomProperty<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: CowArcStr<'i>,
pub value: TokenList<'i>,
}
impl<'i> CustomProperty<'i> {
pub fn parse<'t, T>(
name: CowArcStr<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<T>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
TokenList::parse(input, options, 0)
})?;
Ok(CustomProperty { name, value })
}
}
#[derive(Debug, Clone, PartialEq, Visit)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UnparsedProperty<'i> {
pub property_id: PropertyId<'i>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub value: TokenList<'i>,
}
impl<'i> UnparsedProperty<'i> {
pub fn parse<'t, T>(
property_id: PropertyId<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<T>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
TokenList::parse(input, options, 0)
})?;
Ok(UnparsedProperty { property_id, value })
}
pub(crate) fn get_prefixed(&self, targets: Option<Browsers>, feature: Feature) -> UnparsedProperty<'i> {
let mut clone = self.clone();
if self.property_id.prefix().contains(VendorPrefix::None) {
if let Some(targets) = targets {
clone.property_id = clone.property_id.with_prefix(feature.prefixes_for(targets))
}
}
clone
}
pub fn with_property_id(&self, property_id: PropertyId<'i>) -> UnparsedProperty<'i> {
UnparsedProperty {
property_id,
value: self.value.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Visit)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TokenList<'i>(#[cfg_attr(feature = "serde", serde(borrow))] pub Vec<TokenOrValue<'i>>);
#[derive(Debug, Clone, PartialEq, Visit)]
#[visit(visit_token, TOKENS)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
pub enum TokenOrValue<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
Token(Token<'i>),
Color(CssColor),
UnresolvedColor(UnresolvedColor<'i>),
Url(Url<'i>),
Var(Variable<'i>),
Function(Function<'i>),
Length(LengthValue),
Angle(Angle),
Time(Time),
Resolution(Resolution),
}
impl<'i> From<Token<'i>> for TokenOrValue<'i> {
fn from(token: Token<'i>) -> TokenOrValue<'i> {
TokenOrValue::Token(token)
}
}
impl<'i> TokenOrValue<'i> {
pub fn is_whitespace(&self) -> bool {
matches!(self, TokenOrValue::Token(Token::WhiteSpace(_)))
}
}
impl<'i> TokenList<'i> {
pub(crate) fn parse<'t, T>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<T>,
depth: usize,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut tokens = vec![];
TokenList::parse_into(input, &mut tokens, options, depth)?;
if tokens.len() >= 2 {
let mut slice = &tokens[..];
if matches!(tokens.first(), Some(token) if token.is_whitespace()) {
slice = &slice[1..];
}
if matches!(tokens.last(), Some(token) if token.is_whitespace()) {
slice = &slice[..slice.len() - 1];
}
return Ok(TokenList(slice.to_vec()));
}
return Ok(TokenList(tokens));
}
fn parse_into<'t, T>(
input: &mut Parser<'i, 't>,
tokens: &mut Vec<TokenOrValue<'i>>,
options: &ParserOptions<T>,
depth: usize,
) -> Result<(), ParseError<'i, ParserError<'i>>> {
if depth > 500 {
return Err(input.new_custom_error(ParserError::MaximumNestingDepth));
}
let mut last_is_delim = false;
let mut last_is_whitespace = false;
loop {
let state = input.state();
match input.next_including_whitespace_and_comments() {
Ok(&cssparser::Token::WhiteSpace(..)) | Ok(&cssparser::Token::Comment(..)) => {
if !last_is_delim {
tokens.push(Token::WhiteSpace(" ").into());
last_is_whitespace = true;
}
}
Ok(&cssparser::Token::Function(ref f)) => {
let f = f.into();
if let Some(color) = try_parse_color_token(&f, &state, input) {
tokens.push(TokenOrValue::Color(color));
last_is_delim = false;
last_is_whitespace = false;
} else if let Ok(color) = input.try_parse(|input| UnresolvedColor::parse(&f, input, options)) {
tokens.push(TokenOrValue::UnresolvedColor(color));
last_is_delim = true;
last_is_whitespace = false;
} else if f == "url" {
input.reset(&state);
tokens.push(TokenOrValue::Url(Url::parse(input)?));
last_is_delim = false;
last_is_whitespace = false;
} else if f == "var" {
let var = input.parse_nested_block(|input| {
let var = Variable::parse(input, options, depth + 1)?;
Ok(TokenOrValue::Var(var))
})?;
tokens.push(var);
last_is_delim = true;
last_is_whitespace = false;
} else {
let arguments = input.parse_nested_block(|input| TokenList::parse(input, options, depth + 1))?;
tokens.push(TokenOrValue::Function(Function {
name: Ident(f),
arguments,
}));
last_is_delim = true; last_is_whitespace = false;
}
}
Ok(&cssparser::Token::Hash(ref h)) | Ok(&cssparser::Token::IDHash(ref h)) => {
if let Ok(color) = Color::parse_hash(h.as_bytes()) {
tokens.push(TokenOrValue::Color(color.into()));
} else {
tokens.push(Token::Hash(h.into()).into());
}
last_is_delim = false;
last_is_whitespace = false;
}
Ok(&cssparser::Token::UnquotedUrl(_)) => {
input.reset(&state);
tokens.push(TokenOrValue::Url(Url::parse(input)?));
last_is_delim = false;
last_is_whitespace = false;
}
Ok(token @ &cssparser::Token::ParenthesisBlock)
| Ok(token @ &cssparser::Token::SquareBracketBlock)
| Ok(token @ &cssparser::Token::CurlyBracketBlock) => {
tokens.push(Token::from(token).into());
let closing_delimiter = match token {
cssparser::Token::ParenthesisBlock => Token::CloseParenthesis,
cssparser::Token::SquareBracketBlock => Token::CloseSquareBracket,
cssparser::Token::CurlyBracketBlock => Token::CloseCurlyBracket,
_ => unreachable!(),
};
input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options, depth + 1))?;
tokens.push(closing_delimiter.into());
last_is_delim = true; last_is_whitespace = false;
}
Ok(token @ cssparser::Token::Dimension { .. }) => {
let value = if let Ok(length) = LengthValue::try_from(token) {
TokenOrValue::Length(length)
} else if let Ok(angle) = Angle::try_from(token) {
TokenOrValue::Angle(angle)
} else if let Ok(time) = Time::try_from(token) {
TokenOrValue::Time(time)
} else if let Ok(resolution) = Resolution::try_from(token) {
TokenOrValue::Resolution(resolution)
} else {
TokenOrValue::Token(token.into())
};
tokens.push(value);
last_is_delim = false;
last_is_whitespace = false;
}
Ok(token) => {
last_is_delim = matches!(token, cssparser::Token::Delim(_) | cssparser::Token::Comma);
if last_is_delim && last_is_whitespace {
let last = tokens.last_mut().unwrap();
*last = Token::from(token).into();
} else {
tokens.push(Token::from(token).into());
}
last_is_whitespace = false;
}
Err(_) => break,
}
}
Ok(())
}
}
#[inline]
fn try_parse_color_token<'i, 't>(
f: &CowArcStr<'i>,
state: &ParserState,
input: &mut Parser<'i, 't>,
) -> Option<CssColor> {
match_ignore_ascii_case! { &*f,
"rgb" | "rgba" | "hsl" | "hsla" | "hwb" | "lab" | "lch" | "oklab" | "oklch" | "color" | "color-mix" => {
let s = input.state();
input.reset(&state);
if let Ok(color) = CssColor::parse(input) {
return Some(color)
}
input.reset(&s);
},
_ => {}
}
None
}
impl<'i> TokenList<'i> {
pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
if !dest.minify && self.0.len() == 1 && matches!(self.0.first(), Some(token) if token.is_whitespace()) {
return Ok(());
}
let mut has_whitespace = false;
for (i, token_or_value) in self.0.iter().enumerate() {
has_whitespace = match token_or_value {
TokenOrValue::Color(color) => {
color.to_css(dest)?;
false
}
TokenOrValue::UnresolvedColor(color) => {
color.to_css(dest, is_custom_property)?;
false
}
TokenOrValue::Url(url) => {
if dest.dependencies.is_some() && is_custom_property && !url.is_absolute() {
return Err(dest.error(
PrinterErrorKind::AmbiguousUrlInCustomProperty {
url: url.url.as_ref().to_owned(),
},
url.loc,
));
}
url.to_css(dest)?;
false
}
TokenOrValue::Var(var) => {
var.to_css(dest, is_custom_property)?;
self.write_whitespace_if_needed(i, dest)?
}
TokenOrValue::Function(f) => {
f.to_css(dest, is_custom_property)?;
self.write_whitespace_if_needed(i, dest)?
}
TokenOrValue::Length(v) => {
v.to_css(dest)?;
false
}
TokenOrValue::Angle(v) => {
v.to_css(dest)?;
false
}
TokenOrValue::Time(v) => {
v.to_css(dest)?;
false
}
TokenOrValue::Resolution(v) => {
v.to_css(dest)?;
false
}
TokenOrValue::Token(token) => match token {
Token::Delim(d) => {
if *d == '+' || *d == '-' {
dest.write_char(' ')?;
dest.write_char(*d)?;
dest.write_char(' ')?;
} else {
let ws_before = !has_whitespace && (*d == '/' || *d == '*');
dest.delim(*d, ws_before)?;
}
true
}
Token::Comma => {
dest.delim(',', false)?;
true
}
Token::CloseParenthesis | Token::CloseSquareBracket | Token::CloseCurlyBracket => {
token.to_css(dest)?;
self.write_whitespace_if_needed(i, dest)?
}
Token::Dimension { value, unit, .. } => {
serialize_dimension(*value, unit, dest)?;
false
}
Token::Number { value, .. } => {
value.to_css(dest)?;
false
}
_ => {
token.to_css(dest)?;
matches!(token, Token::WhiteSpace(..))
}
},
};
}
Ok(())
}
#[inline]
fn write_whitespace_if_needed<W>(&self, i: usize, dest: &mut Printer<W>) -> Result<bool, PrinterError>
where
W: std::fmt::Write,
{
if !dest.minify
&& i != self.0.len() - 1
&& !matches!(
self.0[i + 1],
TokenOrValue::Token(Token::Comma) | TokenOrValue::Token(Token::CloseParenthesis)
)
{
dest.write_char(' ')?;
Ok(true)
} else {
Ok(false)
}
}
}
#[derive(Debug, Clone, PartialEq, Visit)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
pub enum Token<'a> {
Ident(#[cfg_attr(feature = "serde", serde(borrow))] CowArcStr<'a>),
AtKeyword(CowArcStr<'a>),
Hash(CowArcStr<'a>),
IDHash(CowArcStr<'a>),
String(CowArcStr<'a>),
UnquotedUrl(CowArcStr<'a>),
Delim(char),
Number {
has_sign: bool,
value: f32,
int_value: Option<i32>,
},
Percentage {
has_sign: bool,
unit_value: f32,
int_value: Option<i32>,
},
Dimension {
has_sign: bool,
value: f32,
int_value: Option<i32>,
unit: CowArcStr<'a>,
},
WhiteSpace(&'a str),
Comment(&'a str),
Colon,
Semicolon,
Comma,
IncludeMatch,
DashMatch,
PrefixMatch,
SuffixMatch,
SubstringMatch,
CDO,
CDC,
Function(CowArcStr<'a>),
ParenthesisBlock,
SquareBracketBlock,
CurlyBracketBlock,
BadUrl(CowArcStr<'a>),
BadString(CowArcStr<'a>),
CloseParenthesis,
CloseSquareBracket,
CloseCurlyBracket,
}
impl<'a> From<&cssparser::Token<'a>> for Token<'a> {
#[inline]
fn from(t: &cssparser::Token<'a>) -> Token<'a> {
match t {
cssparser::Token::Ident(x) => Token::Ident(x.into()),
cssparser::Token::AtKeyword(x) => Token::AtKeyword(x.into()),
cssparser::Token::Hash(x) => Token::Hash(x.into()),
cssparser::Token::IDHash(x) => Token::IDHash(x.into()),
cssparser::Token::QuotedString(x) => Token::String(x.into()),
cssparser::Token::UnquotedUrl(x) => Token::UnquotedUrl(x.into()),
cssparser::Token::Function(x) => Token::Function(x.into()),
cssparser::Token::BadUrl(x) => Token::BadUrl(x.into()),
cssparser::Token::BadString(x) => Token::BadString(x.into()),
cssparser::Token::Delim(c) => Token::Delim(*c),
cssparser::Token::Number {
has_sign,
value,
int_value,
} => Token::Number {
has_sign: *has_sign,
value: *value,
int_value: *int_value,
},
cssparser::Token::Dimension {
has_sign,
value,
int_value,
unit,
} => Token::Dimension {
has_sign: *has_sign,
value: *value,
int_value: *int_value,
unit: unit.into(),
},
cssparser::Token::Percentage {
has_sign,
unit_value,
int_value,
} => Token::Percentage {
has_sign: *has_sign,
unit_value: *unit_value,
int_value: *int_value,
},
cssparser::Token::WhiteSpace(w) => Token::WhiteSpace(w),
cssparser::Token::Comment(c) => Token::Comment(c),
cssparser::Token::Colon => Token::Colon,
cssparser::Token::Semicolon => Token::Semicolon,
cssparser::Token::Comma => Token::Comma,
cssparser::Token::IncludeMatch => Token::IncludeMatch,
cssparser::Token::DashMatch => Token::DashMatch,
cssparser::Token::PrefixMatch => Token::PrefixMatch,
cssparser::Token::SuffixMatch => Token::SuffixMatch,
cssparser::Token::SubstringMatch => Token::SubstringMatch,
cssparser::Token::CDO => Token::CDO,
cssparser::Token::CDC => Token::CDC,
cssparser::Token::ParenthesisBlock => Token::ParenthesisBlock,
cssparser::Token::SquareBracketBlock => Token::SquareBracketBlock,
cssparser::Token::CurlyBracketBlock => Token::CurlyBracketBlock,
cssparser::Token::CloseParenthesis => Token::CloseParenthesis,
cssparser::Token::CloseSquareBracket => Token::CloseSquareBracket,
cssparser::Token::CloseCurlyBracket => Token::CloseCurlyBracket,
}
}
}
impl<'a> ToCss for Token<'a> {
#[inline]
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
use cssparser::ToCss;
match self {
Token::Ident(x) => cssparser::Token::Ident(x.as_ref().into()).to_css(dest)?,
Token::AtKeyword(x) => cssparser::Token::AtKeyword(x.as_ref().into()).to_css(dest)?,
Token::Hash(x) => cssparser::Token::Hash(x.as_ref().into()).to_css(dest)?,
Token::IDHash(x) => cssparser::Token::IDHash(x.as_ref().into()).to_css(dest)?,
Token::String(x) => cssparser::Token::QuotedString(x.as_ref().into()).to_css(dest)?,
Token::UnquotedUrl(x) => cssparser::Token::UnquotedUrl(x.as_ref().into()).to_css(dest)?,
Token::Function(x) => cssparser::Token::Function(x.as_ref().into()).to_css(dest)?,
Token::BadUrl(x) => cssparser::Token::BadUrl(x.as_ref().into()).to_css(dest)?,
Token::BadString(x) => cssparser::Token::BadString(x.as_ref().into()).to_css(dest)?,
Token::Delim(c) => cssparser::Token::Delim(*c).to_css(dest)?,
Token::Number {
has_sign,
value,
int_value,
} => cssparser::Token::Number {
has_sign: *has_sign,
value: *value,
int_value: *int_value,
}
.to_css(dest)?,
Token::Dimension {
has_sign,
value,
int_value,
unit,
} => cssparser::Token::Dimension {
has_sign: *has_sign,
value: *value,
int_value: *int_value,
unit: unit.as_ref().into(),
}
.to_css(dest)?,
Token::Percentage {
has_sign,
unit_value,
int_value,
} => cssparser::Token::Percentage {
has_sign: *has_sign,
unit_value: *unit_value,
int_value: *int_value,
}
.to_css(dest)?,
Token::WhiteSpace(w) => cssparser::Token::WhiteSpace(w).to_css(dest)?,
Token::Comment(c) => cssparser::Token::Comment(c).to_css(dest)?,
Token::Colon => cssparser::Token::Colon.to_css(dest)?,
Token::Semicolon => cssparser::Token::Semicolon.to_css(dest)?,
Token::Comma => cssparser::Token::Comma.to_css(dest)?,
Token::IncludeMatch => cssparser::Token::IncludeMatch.to_css(dest)?,
Token::DashMatch => cssparser::Token::DashMatch.to_css(dest)?,
Token::PrefixMatch => cssparser::Token::PrefixMatch.to_css(dest)?,
Token::SuffixMatch => cssparser::Token::SuffixMatch.to_css(dest)?,
Token::SubstringMatch => cssparser::Token::SubstringMatch.to_css(dest)?,
Token::CDO => cssparser::Token::CDO.to_css(dest)?,
Token::CDC => cssparser::Token::CDC.to_css(dest)?,
Token::ParenthesisBlock => cssparser::Token::ParenthesisBlock.to_css(dest)?,
Token::SquareBracketBlock => cssparser::Token::SquareBracketBlock.to_css(dest)?,
Token::CurlyBracketBlock => cssparser::Token::CurlyBracketBlock.to_css(dest)?,
Token::CloseParenthesis => cssparser::Token::CloseParenthesis.to_css(dest)?,
Token::CloseSquareBracket => cssparser::Token::CloseSquareBracket.to_css(dest)?,
Token::CloseCurlyBracket => cssparser::Token::CloseCurlyBracket.to_css(dest)?,
}
Ok(())
}
}
impl<'i> TokenList<'i> {
pub(crate) fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
let mut fallbacks = ColorFallbackKind::empty();
for token in &self.0 {
match token {
TokenOrValue::Color(color) => {
fallbacks |= color.get_possible_fallbacks(targets);
}
TokenOrValue::Function(f) => {
fallbacks |= f.arguments.get_necessary_fallbacks(targets);
}
TokenOrValue::Var(v) => {
if let Some(fallback) = &v.fallback {
fallbacks |= fallback.get_necessary_fallbacks(targets);
}
}
_ => {}
}
}
fallbacks
}
pub(crate) fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
let tokens = self
.0
.iter()
.map(|token| match token {
TokenOrValue::Color(color) => TokenOrValue::Color(color.get_fallback(kind)),
TokenOrValue::Function(f) => TokenOrValue::Function(f.get_fallback(kind)),
TokenOrValue::Var(v) => TokenOrValue::Var(v.get_fallback(kind)),
_ => token.clone(),
})
.collect();
TokenList(tokens)
}
pub(crate) fn get_fallbacks(&mut self, targets: Browsers) -> Vec<(SupportsCondition<'i>, Self)> {
let mut fallbacks = self.get_necessary_fallbacks(targets);
let lowest_fallback = fallbacks.lowest();
fallbacks.remove(lowest_fallback);
let mut res = Vec::new();
if fallbacks.contains(ColorFallbackKind::P3) {
res.push((
ColorFallbackKind::P3.supports_condition(),
self.get_fallback(ColorFallbackKind::P3),
));
}
if fallbacks.contains(ColorFallbackKind::LAB) {
res.push((
ColorFallbackKind::LAB.supports_condition(),
self.get_fallback(ColorFallbackKind::LAB),
));
}
if !lowest_fallback.is_empty() {
for token in self.0.iter_mut() {
match token {
TokenOrValue::Color(color) => {
*color = color.get_fallback(lowest_fallback);
}
TokenOrValue::Function(f) => *f = f.get_fallback(lowest_fallback),
TokenOrValue::Var(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),
_ => {}
}
}
}
res
}
}
#[derive(Debug, Clone, PartialEq, Visit)]
#[visit(visit_variable, VARIABLES)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Variable<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: DashedIdentReference<'i>,
#[skip_type]
pub fallback: Option<TokenList<'i>>,
}
impl<'i> Variable<'i> {
fn parse<'t, T>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<T>,
depth: usize,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let name = DashedIdentReference::parse_with_options(input, options)?;
let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
Some(TokenList::parse(input, options, depth)?)
} else {
None
};
Ok(Variable { name, fallback })
}
fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
dest.write_str("var(")?;
self.name.to_css(dest)?;
if let Some(fallback) = &self.fallback {
dest.delim(',', false)?;
fallback.to_css(dest, is_custom_property)?;
}
dest.write_char(')')
}
fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
Variable {
name: self.name.clone(),
fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
}
}
}
#[derive(Debug, Clone, PartialEq, Visit)]
#[visit(visit_function, FUNCTIONS)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Function<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: Ident<'i>,
#[skip_type]
pub arguments: TokenList<'i>,
}
impl<'i> Function<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.name.to_css(dest)?;
dest.write_char('(')?;
self.arguments.to_css(dest, is_custom_property)?;
dest.write_char(')')
}
fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
Function {
name: self.name.clone(),
arguments: self.arguments.get_fallback(kind),
}
}
}
#[derive(Debug, Clone, PartialEq, Visit)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "lowercase")
)]
pub enum UnresolvedColor<'i> {
RGB {
r: f32,
g: f32,
b: f32,
#[cfg_attr(feature = "serde", serde(borrow))]
#[skip_type]
alpha: TokenList<'i>,
},
HSL {
h: f32,
s: f32,
l: f32,
#[cfg_attr(feature = "serde", serde(borrow))]
#[skip_type]
alpha: TokenList<'i>,
},
}
impl<'i> UnresolvedColor<'i> {
fn parse<'t, T>(
f: &CowArcStr<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<T>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let parser = ComponentParser::new(false);
match_ignore_ascii_case! { &*f,
"rgb" => {
input.parse_nested_block(|input| {
let (r, g, b) = parse_rgb_components(input, &parser)?;
input.expect_delim('/')?;
let alpha = TokenList::parse(input, options, 0)?;
Ok(UnresolvedColor::RGB { r, g, b, alpha })
})
},
"hsl" => {
input.parse_nested_block(|input| {
let (h, s, l) = parse_hsl_hwb_components(input, &parser)?;
input.expect_delim('/')?;
let alpha = TokenList::parse(input, options, 0)?;
Ok(UnresolvedColor::HSL { h, s, l, alpha })
})
},
_ => Err(input.new_custom_error(ParserError::InvalidValue))
}
}
fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
#[inline]
fn c(c: &f32) -> i32 {
(c * 255.0).round().clamp(0.0, 255.0) as i32
}
match self {
UnresolvedColor::RGB { r, g, b, alpha } => {
if let Some(targets) = dest.targets {
if !compat::Feature::SpaceSeparatedColorFunction.is_compatible(targets) {
dest.write_str("rgba(")?;
c(r).to_css(dest)?;
dest.delim(',', false)?;
c(g).to_css(dest)?;
dest.delim(',', false)?;
c(b).to_css(dest)?;
dest.delim(',', false)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')?;
return Ok(());
}
}
dest.write_str("rgb(")?;
c(r).to_css(dest)?;
dest.write_char(' ')?;
c(g).to_css(dest)?;
dest.write_char(' ')?;
c(b).to_css(dest)?;
dest.delim('/', true)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')
}
UnresolvedColor::HSL { h, s, l, alpha } => {
if let Some(targets) = dest.targets {
if !compat::Feature::SpaceSeparatedColorFunction.is_compatible(targets) {
dest.write_str("hsla(")?;
h.to_css(dest)?;
dest.delim(',', false)?;
Percentage(*s).to_css(dest)?;
dest.delim(',', false)?;
Percentage(*l).to_css(dest)?;
dest.delim(',', false)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')?;
return Ok(());
}
}
dest.write_str("hsl(")?;
h.to_css(dest)?;
dest.write_char(' ')?;
Percentage(*s).to_css(dest)?;
dest.write_char(' ')?;
Percentage(*l).to_css(dest)?;
dest.delim('/', true)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')
}
}
}
}