use crate::error::{ParserError, PrinterError, PrinterErrorKind};
use crate::macros::enum_property;
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::properties::PropertyId;
use crate::rules::supports::SupportsCondition;
use crate::stylesheet::ParserOptions;
use crate::targets::{should_compile, Features, Targets};
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, LightDarkColor,
HSL, RGB, RGBA,
};
use crate::values::ident::{CustomIdent, DashedIdent, DashedIdentReference, Ident};
use crate::values::length::{serialize_dimension, LengthValue};
use crate::values::number::CSSInteger;
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;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::color::parse_hash_color;
use cssparser::*;
use super::AnimationName;
#[cfg(feature = "serde")]
use crate::serialization::ValueWrapper;
#[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))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct CustomProperty<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: CustomPropertyName<'i>,
pub value: TokenList<'i>,
}
impl<'i> CustomProperty<'i> {
pub fn parse<'t>(
name: CustomPropertyName<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
) -> 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, Eq, Hash)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum CustomPropertyName<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
Custom(DashedIdent<'i>),
Unknown(Ident<'i>),
}
impl<'i> From<CowArcStr<'i>> for CustomPropertyName<'i> {
fn from(name: CowArcStr<'i>) -> Self {
if name.starts_with("--") {
CustomPropertyName::Custom(DashedIdent(name))
} else {
CustomPropertyName::Unknown(Ident(name))
}
}
}
impl<'i> From<CowRcStr<'i>> for CustomPropertyName<'i> {
fn from(name: CowRcStr<'i>) -> Self {
CustomPropertyName::from(CowArcStr::from(name))
}
}
impl<'i> AsRef<str> for CustomPropertyName<'i> {
#[inline]
fn as_ref(&self) -> &str {
match self {
CustomPropertyName::Custom(c) => c.as_ref(),
CustomPropertyName::Unknown(u) => u.as_ref(),
}
}
}
impl<'i> ToCss for CustomPropertyName<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
CustomPropertyName::Custom(c) => c.to_css(dest),
CustomPropertyName::Unknown(u) => u.to_css(dest),
}
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'i, 'de: 'i> serde::Deserialize<'de> for CustomPropertyName<'i> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name = CowArcStr::deserialize(deserializer)?;
Ok(name.into())
}
}
#[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(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
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>(
property_id: PropertyId<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
) -> 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: Targets, feature: Feature) -> UnparsedProperty<'i> {
let mut clone = self.clone();
let prefix = self.property_id.prefix();
clone.property_id = clone.property_id.with_prefix(targets.prefixes(prefix.or_none(), feature));
clone
}
pub fn with_property_id(&self, property_id: PropertyId<'i>) -> UnparsedProperty<'i> {
UnparsedProperty {
property_id,
value: self.value.clone(),
}
}
#[cfg(feature = "substitute_variables")]
#[cfg_attr(docsrs, doc(cfg(feature = "substitute_variables")))]
pub fn substitute_variables<'x>(
mut self,
vars: &std::collections::HashMap<&str, TokenList<'i>>,
) -> Result<super::Property<'x>, ()> {
use super::Property;
use crate::stylesheet::PrinterOptions;
use static_self::IntoOwned;
self.value.substitute_variables(vars);
let mut css = String::new();
let mut dest = Printer::new(&mut css, PrinterOptions::default());
self.value.to_css(&mut dest, false).unwrap();
let property =
Property::parse_string(self.property_id.clone(), &css, ParserOptions::default()).map_err(|_| ())?;
Ok(property.into_owned())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "visitor", derive(Visit), visit(visit_token_list, TOKENS))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct TokenList<'i>(#[cfg_attr(feature = "serde", serde(borrow))] pub Vec<TokenOrValue<'i>>);
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit), visit(visit_token, TOKENS), visit_types(TOKENS | COLORS | URLS | VARIABLES | ENVIRONMENT_VARIABLES | FUNCTIONS | LENGTHS | ANGLES | TIMES | RESOLUTIONS | DASHED_IDENTS))]
#[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 TokenOrValue<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
Token(Token<'i>),
Color(CssColor),
UnresolvedColor(UnresolvedColor<'i>),
Url(Url<'i>),
Var(Variable<'i>),
Env(EnvironmentVariable<'i>),
Function(Function<'i>),
Length(LengthValue),
Angle(Angle),
Time(Time),
Resolution(Resolution),
DashedIdent(DashedIdent<'i>),
AnimationName(AnimationName<'i>),
}
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<'a> Eq for TokenOrValue<'a> {}
impl<'a> std::hash::Hash for TokenOrValue<'a> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let tag = std::mem::discriminant(self);
tag.hash(state);
match self {
TokenOrValue::Token(t) => t.hash(state),
_ => {
}
}
}
}
impl<'i> ParseWithOptions<'i> for TokenList<'i> {
fn parse_with_options<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
TokenList::parse(input, options, 0)
}
}
impl<'i> TokenList<'i> {
pub(crate) fn parse<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
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));
}
pub(crate) fn parse_raw<'t>(
input: &mut Parser<'i, 't>,
tokens: &mut Vec<TokenOrValue<'i>>,
options: &ParserOptions<'_, 'i>,
depth: usize,
) -> Result<(), ParseError<'i, ParserError<'i>>> {
if depth > 500 {
return Err(input.new_custom_error(ParserError::MaximumNestingDepth));
}
loop {
let state = input.state();
match input.next_including_whitespace_and_comments() {
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_raw(input, tokens, options, depth + 1))?;
tokens.push(closing_delimiter.into());
}
Ok(token @ &cssparser::Token::Function(_)) => {
tokens.push(Token::from(token).into());
input.parse_nested_block(|input| TokenList::parse_raw(input, tokens, options, depth + 1))?;
tokens.push(Token::CloseParenthesis.into());
}
Ok(token) if token.is_parse_error() => {
return Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),
location: state.source_location(),
})
}
Ok(token) => {
tokens.push(Token::from(token).into());
}
Err(_) => break,
}
}
Ok(())
}
fn parse_into<'t>(
input: &mut Parser<'i, 't>,
tokens: &mut Vec<TokenOrValue<'i>>,
options: &ParserOptions<'_, 'i>,
depth: usize,
) -> Result<(), ParseError<'i, ParserError<'i>>> {
if depth > 500 {
return Err(input.new_custom_error(ParserError::MaximumNestingDepth));
}
loop {
let state = input.state();
match input.next_including_whitespace_and_comments() {
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));
} else if let Ok(color) = input.try_parse(|input| UnresolvedColor::parse(&f, input, options)) {
tokens.push(TokenOrValue::UnresolvedColor(color));
} else if f == "url" {
input.reset(&state);
tokens.push(TokenOrValue::Url(Url::parse(input)?));
} 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);
} else if f == "env" {
let env = input.parse_nested_block(|input| {
let env = EnvironmentVariable::parse_nested(input, options, depth + 1)?;
Ok(TokenOrValue::Env(env))
})?;
tokens.push(env);
} else {
let arguments = input.parse_nested_block(|input| TokenList::parse(input, options, depth + 1))?;
tokens.push(TokenOrValue::Function(Function {
name: Ident(f),
arguments,
}));
}
}
Ok(&cssparser::Token::Hash(ref h)) | Ok(&cssparser::Token::IDHash(ref h)) => {
if let Ok((r, g, b, a)) = parse_hash_color(h.as_bytes()) {
tokens.push(TokenOrValue::Color(CssColor::RGBA(RGBA::new(r, g, b, a))));
} else {
tokens.push(Token::Hash(h.into()).into());
}
}
Ok(&cssparser::Token::UnquotedUrl(_)) => {
input.reset(&state);
tokens.push(TokenOrValue::Url(Url::parse(input)?));
}
Ok(&cssparser::Token::Ident(ref name)) if name.starts_with("--") => {
tokens.push(TokenOrValue::DashedIdent(name.into()));
}
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());
}
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);
}
Ok(token) if token.is_parse_error() => {
return Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),
location: state.source_location(),
})
}
Ok(token) => {
tokens.push(Token::from(token).into());
}
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" | "light-dark" => {
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,
{
for token_or_value in self.0.iter() {
match token_or_value {
TokenOrValue::Color(color) => {
color.to_css(dest)?;
}
TokenOrValue::UnresolvedColor(color) => {
color.to_css(dest, is_custom_property)?;
}
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)?;
}
TokenOrValue::Var(var) => {
var.to_css(dest, is_custom_property)?;
}
TokenOrValue::Env(env) => {
env.to_css(dest, is_custom_property)?;
}
TokenOrValue::Function(f) => {
f.to_css(dest, is_custom_property)?;
}
TokenOrValue::Length(v) => {
let (value, unit) = v.to_unit_value();
serialize_dimension(value, unit, dest)?;
}
TokenOrValue::Angle(v) => {
v.to_css(dest)?;
}
TokenOrValue::Time(v) => {
v.to_css(dest)?;
}
TokenOrValue::Resolution(v) => {
v.to_css(dest)?;
}
TokenOrValue::DashedIdent(v) => {
v.to_css(dest)?;
}
TokenOrValue::AnimationName(v) => {
v.to_css(dest)?;
}
TokenOrValue::Token(token) => match token {
Token::Dimension { value, unit, .. } => {
serialize_dimension(*value, unit, dest)?;
}
Token::Number { value, .. } => {
value.to_css(dest)?;
}
_ => {
token.to_css(dest)?;
}
},
};
}
Ok(())
}
pub(crate) fn to_css_raw<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
for token_or_value in &self.0 {
match token_or_value {
TokenOrValue::Token(token) => {
token.to_css(dest)?;
}
_ => {
return Err(PrinterError {
kind: PrinterErrorKind::FmtError,
loc: None,
})
}
}
}
Ok(())
}
pub(crate) fn starts_with_whitespace(&self) -> bool {
matches!(self.0.get(0), Some(TokenOrValue::Token(Token::WhiteSpace(_))))
}
}
#[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", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Token<'a> {
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
Ident(#[cfg_attr(feature = "serde", serde(borrow))] CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
AtKeyword(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
Hash(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(rename = "id-hash", with = "ValueWrapper::<CowArcStr>"))]
IDHash(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
String(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
UnquotedUrl(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<char>"))]
Delim(char),
Number {
#[cfg_attr(feature = "serde", serde(skip))]
has_sign: bool,
value: f32,
#[cfg_attr(feature = "serde", serde(skip))]
int_value: Option<i32>,
},
Percentage {
#[cfg_attr(feature = "serde", serde(skip))]
has_sign: bool,
#[cfg_attr(feature = "serde", serde(rename = "value"))]
unit_value: f32,
#[cfg_attr(feature = "serde", serde(skip))]
int_value: Option<i32>,
},
Dimension {
#[cfg_attr(feature = "serde", serde(skip))]
has_sign: bool,
value: f32,
#[cfg_attr(feature = "serde", serde(skip))]
int_value: Option<i32>,
unit: CowArcStr<'a>,
},
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
WhiteSpace(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
Comment(CowArcStr<'a>),
Colon,
Semicolon,
Comma,
IncludeMatch,
DashMatch,
PrefixMatch,
SuffixMatch,
SubstringMatch,
#[cfg_attr(feature = "serde", serde(rename = "cdo"))]
CDO,
#[cfg_attr(feature = "serde", serde(rename = "cdc"))]
CDC,
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
Function(CowArcStr<'a>),
ParenthesisBlock,
SquareBracketBlock,
CurlyBracketBlock,
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
BadUrl(CowArcStr<'a>),
#[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
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).into()),
cssparser::Token::Comment(c) => Token::Comment((*c).into()),
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) => {
if dest.minify {
dest.write_char(' ')?;
} else {
dest.write_str(&w)?;
}
}
Token::Comment(c) => {
if !dest.minify {
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<'a> Eq for Token<'a> {}
impl<'a> std::hash::Hash for Token<'a> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let tag = std::mem::discriminant(self);
tag.hash(state);
match self {
Token::Ident(x) => x.hash(state),
Token::AtKeyword(x) => x.hash(state),
Token::Hash(x) => x.hash(state),
Token::IDHash(x) => x.hash(state),
Token::String(x) => x.hash(state),
Token::UnquotedUrl(x) => x.hash(state),
Token::Function(x) => x.hash(state),
Token::BadUrl(x) => x.hash(state),
Token::BadString(x) => x.hash(state),
Token::Delim(x) => x.hash(state),
Token::Number {
has_sign,
value,
int_value,
} => {
has_sign.hash(state);
integer_decode(*value).hash(state);
int_value.hash(state);
}
Token::Dimension {
has_sign,
value,
int_value,
unit,
} => {
has_sign.hash(state);
integer_decode(*value).hash(state);
int_value.hash(state);
unit.hash(state);
}
Token::Percentage {
has_sign,
unit_value,
int_value,
} => {
has_sign.hash(state);
integer_decode(*unit_value).hash(state);
int_value.hash(state);
}
Token::WhiteSpace(w) => w.hash(state),
Token::Comment(c) => c.hash(state),
Token::Colon
| Token::Semicolon
| Token::Comma
| Token::IncludeMatch
| Token::DashMatch
| Token::PrefixMatch
| Token::SuffixMatch
| Token::SubstringMatch
| Token::CDO
| Token::CDC
| Token::ParenthesisBlock
| Token::SquareBracketBlock
| Token::CurlyBracketBlock
| Token::CloseParenthesis
| Token::CloseSquareBracket
| Token::CloseCurlyBracket => {}
}
}
}
fn integer_decode(v: f32) -> (u32, i16, i8) {
let bits: u32 = f32::to_bits(v);
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
let mantissa = if exponent == 0 {
(bits & 0x7fffff) << 1
} else {
(bits & 0x7fffff) | 0x800000
};
exponent -= 127 + 23;
(mantissa, exponent, sign)
}
impl<'i> TokenList<'i> {
pub(crate) fn get_necessary_fallbacks(&self, targets: Targets) -> 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);
}
}
TokenOrValue::Env(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)),
TokenOrValue::Env(e) => TokenOrValue::Env(e.get_fallback(kind)),
_ => token.clone(),
})
.collect();
TokenList(tokens)
}
pub(crate) fn get_fallbacks(&mut self, targets: Targets) -> 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),
TokenOrValue::Env(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),
_ => {}
}
}
}
res
}
pub(crate) fn get_features(&self) -> Features {
let mut features = Features::empty();
for token in &self.0 {
match token {
TokenOrValue::Color(color) => {
features |= color.get_features();
}
TokenOrValue::UnresolvedColor(unresolved_color) => {
features |= Features::SpaceSeparatedColorNotation;
match unresolved_color {
UnresolvedColor::LightDark { light, dark } => {
features |= Features::LightDark;
features |= light.get_features();
features |= dark.get_features();
}
_ => {}
}
}
TokenOrValue::Function(f) => {
features |= f.arguments.get_features();
}
TokenOrValue::Var(v) => {
if let Some(fallback) = &v.fallback {
features |= fallback.get_features();
}
}
TokenOrValue::Env(v) => {
if let Some(fallback) = &v.fallback {
features |= fallback.get_features();
}
}
_ => {}
}
}
features
}
#[cfg(feature = "substitute_variables")]
#[cfg_attr(docsrs, doc(cfg(feature = "substitute_variables")))]
pub fn substitute_variables(&mut self, vars: &std::collections::HashMap<&str, TokenList<'i>>) {
self.visit(&mut VarInliner { vars }).unwrap()
}
}
#[cfg(feature = "substitute_variables")]
struct VarInliner<'a, 'i> {
vars: &'a std::collections::HashMap<&'a str, TokenList<'i>>,
}
#[cfg(feature = "substitute_variables")]
impl<'a, 'i> crate::visitor::Visitor<'i> for VarInliner<'a, 'i> {
type Error = std::convert::Infallible;
fn visit_types(&self) -> crate::visitor::VisitTypes {
crate::visit_types!(TOKENS | VARIABLES)
}
fn visit_token_list(&mut self, tokens: &mut TokenList<'i>) -> Result<(), Self::Error> {
let mut i = 0;
let mut seen = std::collections::HashSet::new();
while i < tokens.0.len() {
let token = &mut tokens.0[i];
token.visit(self).unwrap();
if let TokenOrValue::Var(var) = token {
if let Some(value) = self.vars.get(var.name.ident.0.as_ref()) {
if seen.insert(var.name.ident.0.clone()) {
tokens.0.splice(i..i + 1, value.0.iter().cloned());
continue;
}
} else if let Some(fallback) = &var.fallback {
let fallback = fallback.0.clone();
if seen.insert(var.name.ident.0.clone()) {
tokens.0.splice(i..i + 1, fallback.into_iter());
continue;
}
}
}
seen.clear();
i += 1;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "visitor", visit(visit_variable, VARIABLES))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Variable<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: DashedIdentReference<'i>,
pub fallback: Option<TokenList<'i>>,
}
impl<'i> Variable<'i> {
fn parse<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
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.write_char(',')?;
if !fallback.starts_with_whitespace() {
dest.whitespace()?;
}
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)]
#[cfg_attr(
feature = "visitor",
derive(Visit),
visit(visit_environment_variable, ENVIRONMENT_VARIABLES)
)]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct EnvironmentVariable<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: EnvironmentVariableName<'i>,
#[cfg_attr(feature = "serde", serde(default))]
pub indices: Vec<CSSInteger>,
pub fallback: Option<TokenList<'i>>,
}
#[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", rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum EnvironmentVariableName<'i> {
#[cfg_attr(
feature = "serde",
serde(with = "crate::serialization::ValueWrapper::<UAEnvironmentVariable>")
)]
UA(UAEnvironmentVariable),
#[cfg_attr(feature = "serde", serde(borrow))]
Custom(DashedIdentReference<'i>),
#[cfg_attr(feature = "serde", serde(with = "crate::serialization::ValueWrapper::<CustomIdent>"))]
Unknown(CustomIdent<'i>),
}
enum_property! {
pub enum UAEnvironmentVariable {
SafeAreaInsetTop,
SafeAreaInsetRight,
SafeAreaInsetBottom,
SafeAreaInsetLeft,
ViewportSegmentWidth,
ViewportSegmentHeight,
ViewportSegmentTop,
ViewportSegmentLeft,
ViewportSegmentBottom,
ViewportSegmentRight,
}
}
impl<'i> EnvironmentVariableName<'i> {
pub fn name(&self) -> &str {
match self {
EnvironmentVariableName::UA(ua) => ua.as_str(),
EnvironmentVariableName::Custom(c) => c.ident.as_ref(),
EnvironmentVariableName::Unknown(u) => u.0.as_ref(),
}
}
}
impl<'i> Parse<'i> for EnvironmentVariableName<'i> {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(ua) = input.try_parse(UAEnvironmentVariable::parse) {
return Ok(EnvironmentVariableName::UA(ua));
}
if let Ok(dashed) =
input.try_parse(|input| DashedIdentReference::parse_with_options(input, &ParserOptions::default()))
{
return Ok(EnvironmentVariableName::Custom(dashed));
}
let ident = CustomIdent::parse(input)?;
return Ok(EnvironmentVariableName::Unknown(ident));
}
}
impl<'i> ToCss for EnvironmentVariableName<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
EnvironmentVariableName::UA(ua) => ua.to_css(dest),
EnvironmentVariableName::Custom(custom) => custom.to_css(dest),
EnvironmentVariableName::Unknown(unknown) => unknown.to_css(dest),
}
}
}
impl<'i> EnvironmentVariable<'i> {
pub(crate) fn parse<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
depth: usize,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
input.expect_function_matching("env")?;
input.parse_nested_block(|input| Self::parse_nested(input, options, depth))
}
pub(crate) fn parse_nested<'t>(
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
depth: usize,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let name = EnvironmentVariableName::parse(input)?;
let mut indices = Vec::new();
while let Ok(index) = input.try_parse(CSSInteger::parse) {
indices.push(index);
}
let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
Some(TokenList::parse(input, options, depth + 1)?)
} else {
None
};
Ok(EnvironmentVariable {
name,
indices,
fallback,
})
}
pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
dest.write_str("env(")?;
self.name.to_css(dest)?;
for item in &self.indices {
dest.write_char(' ')?;
item.to_css(dest)?;
}
if let Some(fallback) = &self.fallback {
dest.write_char(',')?;
if !fallback.starts_with_whitespace() {
dest.whitespace()?;
}
fallback.to_css(dest, is_custom_property)?;
}
dest.write_char(')')
}
fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
EnvironmentVariable {
name: self.name.clone(),
indices: self.indices.clone(),
fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "visitor", visit(visit_function, FUNCTIONS))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct Function<'i> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: Ident<'i>,
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)]
#[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", rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum UnresolvedColor<'i> {
RGB {
r: f32,
g: f32,
b: f32,
#[cfg_attr(feature = "serde", serde(borrow))]
alpha: TokenList<'i>,
},
HSL {
h: f32,
s: f32,
l: f32,
#[cfg_attr(feature = "serde", serde(borrow))]
alpha: TokenList<'i>,
},
#[cfg_attr(feature = "serde", serde(rename = "light-dark"))]
LightDark {
light: TokenList<'i>,
dark: TokenList<'i>,
},
}
impl<'i> LightDarkColor for UnresolvedColor<'i> {
#[inline]
fn light_dark(light: Self, dark: Self) -> Self {
UnresolvedColor::LightDark {
light: TokenList(vec![TokenOrValue::UnresolvedColor(light)]),
dark: TokenList(vec![TokenOrValue::UnresolvedColor(dark)]),
}
}
}
impl<'i> UnresolvedColor<'i> {
fn parse<'t>(
f: &CowArcStr<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut parser = ComponentParser::new(false);
match_ignore_ascii_case! { &*f,
"rgb" => {
input.parse_nested_block(|input| {
parser.parse_relative::<RGB, _, _>(input, |input, parser| {
let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
if is_legacy {
return Err(input.new_custom_error(ParserError::InvalidValue))
}
input.expect_delim('/')?;
let alpha = TokenList::parse(input, options, 0)?;
Ok(UnresolvedColor::RGB { r, g, b, alpha })
})
})
},
"hsl" => {
input.parse_nested_block(|input| {
parser.parse_relative::<HSL, _, _>(input, |input, parser| {
let (h, s, l, is_legacy) = parse_hsl_hwb_components::<HSL>(input, parser, false)?;
if is_legacy {
return Err(input.new_custom_error(ParserError::InvalidValue))
}
input.expect_delim('/')?;
let alpha = TokenList::parse(input, options, 0)?;
Ok(UnresolvedColor::HSL { h, s, l, alpha })
})
})
},
"light-dark" => {
input.parse_nested_block(|input| {
let light = input.parse_until_before(Delimiter::Comma, |input|
TokenList::parse(input, options, 0)
)?;
input.expect_comma()?;
let dark = TokenList::parse(input, options, 0)?;
Ok(UnresolvedColor::LightDark { light, dark })
})
},
_ => 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,
{
match self {
UnresolvedColor::RGB { r, g, b, alpha } => {
if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {
dest.write_str("rgba(")?;
r.to_css(dest)?;
dest.delim(',', false)?;
g.to_css(dest)?;
dest.delim(',', false)?;
b.to_css(dest)?;
dest.delim(',', false)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')?;
return Ok(());
}
dest.write_str("rgb(")?;
r.to_css(dest)?;
dest.write_char(' ')?;
g.to_css(dest)?;
dest.write_char(' ')?;
b.to_css(dest)?;
dest.delim('/', true)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')
}
UnresolvedColor::HSL { h, s, l, alpha } => {
if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {
dest.write_str("hsla(")?;
h.to_css(dest)?;
dest.delim(',', false)?;
Percentage(*s / 100.0).to_css(dest)?;
dest.delim(',', false)?;
Percentage(*l / 100.0).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 / 100.0).to_css(dest)?;
dest.write_char(' ')?;
Percentage(*l / 100.0).to_css(dest)?;
dest.delim('/', true)?;
alpha.to_css(dest, is_custom_property)?;
dest.write_char(')')
}
UnresolvedColor::LightDark { light, dark } => {
if should_compile!(dest.targets.current, LightDark) {
dest.write_str("var(--lightningcss-light")?;
dest.delim(',', false)?;
light.to_css(dest, is_custom_property)?;
dest.write_char(')')?;
dest.whitespace()?;
dest.write_str("var(--lightningcss-dark")?;
dest.delim(',', false)?;
dark.to_css(dest, is_custom_property)?;
return dest.write_char(')');
}
dest.write_str("light-dark(")?;
light.to_css(dest, is_custom_property)?;
dest.delim(',', false)?;
dark.to_css(dest, is_custom_property)?;
dest.write_char(')')
}
}
}
}