1use crate::error::{ParserError, PrinterError, PrinterErrorKind};
4use crate::macros::enum_property;
5use crate::prefixes::Feature;
6use crate::printer::Printer;
7use crate::properties::PropertyId;
8use crate::rules::supports::SupportsCondition;
9use crate::stylesheet::ParserOptions;
10use crate::targets::{should_compile, Features, Targets};
11use crate::traits::{Parse, ParseWithOptions, ToCss};
12use crate::values::angle::Angle;
13use crate::values::color::{
14 parse_hsl_hwb_components, parse_rgb_components, ColorFallbackKind, ComponentParser, CssColor, LightDarkColor,
15 HSL, RGB, RGBA,
16};
17use crate::values::ident::{CustomIdent, DashedIdent, DashedIdentReference, Ident};
18use crate::values::length::{serialize_dimension, LengthValue};
19use crate::values::number::CSSInteger;
20use crate::values::percentage::Percentage;
21use crate::values::resolution::Resolution;
22use crate::values::string::CowArcStr;
23use crate::values::time::Time;
24use crate::values::url::Url;
25#[cfg(feature = "visitor")]
26use crate::visitor::Visit;
27use cssparser::color::parse_hash_color;
28use cssparser::*;
29
30use super::AnimationName;
31#[cfg(feature = "serde")]
32use crate::serialization::ValueWrapper;
33
34#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "visitor", derive(Visit))]
37#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
40pub struct CustomProperty<'i> {
41 #[cfg_attr(feature = "serde", serde(borrow))]
43 pub name: CustomPropertyName<'i>,
44 pub value: TokenList<'i>,
46}
47
48impl<'i> CustomProperty<'i> {
49 pub fn parse<'t>(
51 name: CustomPropertyName<'i>,
52 input: &mut Parser<'i, 't>,
53 options: &ParserOptions<'_, 'i>,
54 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
55 let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
56 TokenList::parse(input, options, 0)
57 })?;
58 Ok(CustomProperty { name, value })
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64#[cfg_attr(feature = "visitor", derive(Visit))]
65#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
66#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
67#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
68pub enum CustomPropertyName<'i> {
69 #[cfg_attr(feature = "serde", serde(borrow))]
71 Custom(DashedIdent<'i>),
72 Unknown(Ident<'i>),
74}
75
76impl<'i> From<CowArcStr<'i>> for CustomPropertyName<'i> {
77 fn from(name: CowArcStr<'i>) -> Self {
78 if name.starts_with("--") {
79 CustomPropertyName::Custom(DashedIdent(name))
80 } else {
81 CustomPropertyName::Unknown(Ident(name))
82 }
83 }
84}
85
86impl<'i> From<CowRcStr<'i>> for CustomPropertyName<'i> {
87 fn from(name: CowRcStr<'i>) -> Self {
88 CustomPropertyName::from(CowArcStr::from(name))
89 }
90}
91
92impl<'i> AsRef<str> for CustomPropertyName<'i> {
93 #[inline]
94 fn as_ref(&self) -> &str {
95 match self {
96 CustomPropertyName::Custom(c) => c.as_ref(),
97 CustomPropertyName::Unknown(u) => u.as_ref(),
98 }
99 }
100}
101
102impl<'i> ToCss for CustomPropertyName<'i> {
103 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
104 where
105 W: std::fmt::Write,
106 {
107 match self {
108 CustomPropertyName::Custom(c) => c.to_css(dest),
109 CustomPropertyName::Unknown(u) => u.to_css(dest),
110 }
111 }
112}
113
114#[cfg(feature = "serde")]
115#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
116impl<'i, 'de: 'i> serde::Deserialize<'de> for CustomPropertyName<'i> {
117 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
118 where
119 D: serde::Deserializer<'de>,
120 {
121 let name = CowArcStr::deserialize(deserializer)?;
122 Ok(name.into())
123 }
124}
125
126#[derive(Debug, Clone, PartialEq)]
132#[cfg_attr(feature = "visitor", derive(Visit))]
133#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
134#[cfg_attr(
135 feature = "serde",
136 derive(serde::Serialize, serde::Deserialize),
137 serde(rename_all = "camelCase")
138)]
139#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
140pub struct UnparsedProperty<'i> {
141 pub property_id: PropertyId<'i>,
143 #[cfg_attr(feature = "serde", serde(borrow))]
145 pub value: TokenList<'i>,
146}
147
148impl<'i> UnparsedProperty<'i> {
149 pub fn parse<'t>(
151 property_id: PropertyId<'i>,
152 input: &mut Parser<'i, 't>,
153 options: &ParserOptions<'_, 'i>,
154 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
155 let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
156 TokenList::parse(input, options, 0)
157 })?;
158 Ok(UnparsedProperty { property_id, value })
159 }
160
161 pub(crate) fn get_prefixed(&self, targets: Targets, feature: Feature) -> UnparsedProperty<'i> {
162 let mut clone = self.clone();
163 let prefix = self.property_id.prefix();
164 clone.property_id = clone.property_id.with_prefix(targets.prefixes(prefix.or_none(), feature));
165 clone
166 }
167
168 pub fn with_property_id(&self, property_id: PropertyId<'i>) -> UnparsedProperty<'i> {
170 UnparsedProperty {
171 property_id,
172 value: self.value.clone(),
173 }
174 }
175
176 #[cfg(feature = "substitute_variables")]
178 #[cfg_attr(docsrs, doc(cfg(feature = "substitute_variables")))]
179 pub fn substitute_variables<'x>(
180 mut self,
181 vars: &std::collections::HashMap<&str, TokenList<'i>>,
182 ) -> Result<super::Property<'x>, ()> {
183 use super::Property;
184 use crate::stylesheet::PrinterOptions;
185 use static_self::IntoOwned;
186
187 self.value.substitute_variables(vars);
189
190 let mut css = String::new();
193 let mut dest = Printer::new(&mut css, PrinterOptions::default());
194 self.value.to_css(&mut dest, false).unwrap();
195 let property =
196 Property::parse_string(self.property_id.clone(), &css, ParserOptions::default()).map_err(|_| ())?;
197 Ok(property.into_owned())
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Hash)]
203#[cfg_attr(feature = "visitor", derive(Visit), visit(visit_token_list, TOKENS))]
204#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
206#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
207pub struct TokenList<'i>(#[cfg_attr(feature = "serde", serde(borrow))] pub Vec<TokenOrValue<'i>>);
208
209#[derive(Debug, Clone, PartialEq)]
211#[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))]
212#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
213#[cfg_attr(
214 feature = "serde",
215 derive(serde::Serialize, serde::Deserialize),
216 serde(tag = "type", content = "value", rename_all = "kebab-case")
217)]
218#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
219pub enum TokenOrValue<'i> {
220 #[cfg_attr(feature = "serde", serde(borrow))]
222 Token(Token<'i>),
223 Color(CssColor),
225 UnresolvedColor(UnresolvedColor<'i>),
227 Url(Url<'i>),
229 Var(Variable<'i>),
231 Env(EnvironmentVariable<'i>),
233 Function(Function<'i>),
235 Length(LengthValue),
237 Angle(Angle),
239 Time(Time),
241 Resolution(Resolution),
243 DashedIdent(DashedIdent<'i>),
245 AnimationName(AnimationName<'i>),
247}
248
249impl<'i> From<Token<'i>> for TokenOrValue<'i> {
250 fn from(token: Token<'i>) -> TokenOrValue<'i> {
251 TokenOrValue::Token(token)
252 }
253}
254
255impl<'i> TokenOrValue<'i> {
256 pub fn is_whitespace(&self) -> bool {
258 matches!(self, TokenOrValue::Token(Token::WhiteSpace(_)))
259 }
260}
261
262impl<'a> Eq for TokenOrValue<'a> {}
263
264impl<'a> std::hash::Hash for TokenOrValue<'a> {
265 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
266 let tag = std::mem::discriminant(self);
267 tag.hash(state);
268 match self {
269 TokenOrValue::Token(t) => t.hash(state),
270 _ => {
271 }
277 }
278 }
279}
280
281impl<'i> ParseWithOptions<'i> for TokenList<'i> {
282 fn parse_with_options<'t>(
283 input: &mut Parser<'i, 't>,
284 options: &ParserOptions<'_, 'i>,
285 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
286 TokenList::parse(input, options, 0)
287 }
288}
289
290impl<'i> TokenList<'i> {
291 pub(crate) fn parse<'t>(
292 input: &mut Parser<'i, 't>,
293 options: &ParserOptions<'_, 'i>,
294 depth: usize,
295 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
296 let mut tokens = vec![];
297 TokenList::parse_into(input, &mut tokens, options, depth)?;
298
299 if tokens.len() >= 2 {
302 let mut slice = &tokens[..];
303 if matches!(tokens.first(), Some(token) if token.is_whitespace()) {
304 slice = &slice[1..];
305 }
306 if matches!(tokens.last(), Some(token) if token.is_whitespace()) {
307 slice = &slice[..slice.len() - 1];
308 }
309 return Ok(TokenList(slice.to_vec()));
310 }
311
312 return Ok(TokenList(tokens));
313 }
314
315 pub(crate) fn parse_raw<'t>(
316 input: &mut Parser<'i, 't>,
317 tokens: &mut Vec<TokenOrValue<'i>>,
318 options: &ParserOptions<'_, 'i>,
319 depth: usize,
320 ) -> Result<(), ParseError<'i, ParserError<'i>>> {
321 if depth > 500 {
322 return Err(input.new_custom_error(ParserError::MaximumNestingDepth));
323 }
324
325 loop {
326 let state = input.state();
327 match input.next_including_whitespace_and_comments() {
328 Ok(token @ &cssparser::Token::ParenthesisBlock)
329 | Ok(token @ &cssparser::Token::SquareBracketBlock)
330 | Ok(token @ &cssparser::Token::CurlyBracketBlock) => {
331 tokens.push(Token::from(token).into());
332 let closing_delimiter = match token {
333 cssparser::Token::ParenthesisBlock => Token::CloseParenthesis,
334 cssparser::Token::SquareBracketBlock => Token::CloseSquareBracket,
335 cssparser::Token::CurlyBracketBlock => Token::CloseCurlyBracket,
336 _ => unreachable!(),
337 };
338
339 input.parse_nested_block(|input| TokenList::parse_raw(input, tokens, options, depth + 1))?;
340 tokens.push(closing_delimiter.into());
341 }
342 Ok(token @ &cssparser::Token::Function(_)) => {
343 tokens.push(Token::from(token).into());
344 input.parse_nested_block(|input| TokenList::parse_raw(input, tokens, options, depth + 1))?;
345 tokens.push(Token::CloseParenthesis.into());
346 }
347 Ok(token) if token.is_parse_error() => {
348 return Err(ParseError {
349 kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),
350 location: state.source_location(),
351 })
352 }
353 Ok(token) => {
354 tokens.push(Token::from(token).into());
355 }
356 Err(_) => break,
357 }
358 }
359
360 Ok(())
361 }
362
363 fn parse_into<'t>(
364 input: &mut Parser<'i, 't>,
365 tokens: &mut Vec<TokenOrValue<'i>>,
366 options: &ParserOptions<'_, 'i>,
367 depth: usize,
368 ) -> Result<(), ParseError<'i, ParserError<'i>>> {
369 if depth > 500 {
370 return Err(input.new_custom_error(ParserError::MaximumNestingDepth));
371 }
372
373 loop {
374 let state = input.state();
375 match input.next_including_whitespace_and_comments() {
376 Ok(&cssparser::Token::Function(ref f)) => {
377 let f = f.into();
379 if let Some(color) = try_parse_color_token(&f, &state, input) {
380 tokens.push(TokenOrValue::Color(color));
381 } else if let Ok(color) = input.try_parse(|input| UnresolvedColor::parse(&f, input, options)) {
382 tokens.push(TokenOrValue::UnresolvedColor(color));
383 } else if f == "url" {
384 input.reset(&state);
385 tokens.push(TokenOrValue::Url(Url::parse(input)?));
386 } else if f == "var" {
387 let var = input.parse_nested_block(|input| {
388 let var = Variable::parse(input, options, depth + 1)?;
389 Ok(TokenOrValue::Var(var))
390 })?;
391 tokens.push(var);
392 } else if f == "env" {
393 let env = input.parse_nested_block(|input| {
394 let env = EnvironmentVariable::parse_nested(input, options, depth + 1)?;
395 Ok(TokenOrValue::Env(env))
396 })?;
397 tokens.push(env);
398 } else {
399 let arguments = input.parse_nested_block(|input| TokenList::parse(input, options, depth + 1))?;
400 tokens.push(TokenOrValue::Function(Function {
401 name: Ident(f),
402 arguments,
403 }));
404 }
405 }
406 Ok(&cssparser::Token::Hash(ref h)) | Ok(&cssparser::Token::IDHash(ref h)) => {
407 if let Ok((r, g, b, a)) = parse_hash_color(h.as_bytes()) {
408 tokens.push(TokenOrValue::Color(CssColor::RGBA(RGBA::new(r, g, b, a))));
409 } else {
410 tokens.push(Token::Hash(h.into()).into());
411 }
412 }
413 Ok(&cssparser::Token::UnquotedUrl(_)) => {
414 input.reset(&state);
415 tokens.push(TokenOrValue::Url(Url::parse(input)?));
416 }
417 Ok(&cssparser::Token::Ident(ref name)) if name.starts_with("--") => {
418 tokens.push(TokenOrValue::DashedIdent(name.into()));
419 }
420 Ok(token @ &cssparser::Token::ParenthesisBlock)
421 | Ok(token @ &cssparser::Token::SquareBracketBlock)
422 | Ok(token @ &cssparser::Token::CurlyBracketBlock) => {
423 tokens.push(Token::from(token).into());
424 let closing_delimiter = match token {
425 cssparser::Token::ParenthesisBlock => Token::CloseParenthesis,
426 cssparser::Token::SquareBracketBlock => Token::CloseSquareBracket,
427 cssparser::Token::CurlyBracketBlock => Token::CloseCurlyBracket,
428 _ => unreachable!(),
429 };
430
431 input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options, depth + 1))?;
432
433 tokens.push(closing_delimiter.into());
434 }
435 Ok(token @ cssparser::Token::Dimension { .. }) => {
436 let value = if let Ok(length) = LengthValue::try_from(token) {
437 TokenOrValue::Length(length)
438 } else if let Ok(angle) = Angle::try_from(token) {
439 TokenOrValue::Angle(angle)
440 } else if let Ok(time) = Time::try_from(token) {
441 TokenOrValue::Time(time)
442 } else if let Ok(resolution) = Resolution::try_from(token) {
443 TokenOrValue::Resolution(resolution)
444 } else {
445 TokenOrValue::Token(token.into())
446 };
447 tokens.push(value);
448 }
449 Ok(token) if token.is_parse_error() => {
450 return Err(ParseError {
451 kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),
452 location: state.source_location(),
453 })
454 }
455 Ok(token) => {
456 tokens.push(Token::from(token).into());
457 }
458 Err(_) => break,
459 }
460 }
461
462 Ok(())
463 }
464}
465
466#[inline]
467fn try_parse_color_token<'i, 't>(
468 f: &CowArcStr<'i>,
469 state: &ParserState,
470 input: &mut Parser<'i, 't>,
471) -> Option<CssColor> {
472 match_ignore_ascii_case! { &*f,
473 "rgb" | "rgba" | "hsl" | "hsla" | "hwb" | "lab" | "lch" | "oklab" | "oklch" | "color" | "color-mix" | "light-dark" => {
474 let s = input.state();
475 input.reset(&state);
476 if let Ok(color) = CssColor::parse(input) {
477 return Some(color)
478 }
479 input.reset(&s);
480 },
481 _ => {}
482 }
483
484 None
485}
486
487impl<'i> TokenList<'i> {
488 pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
489 where
490 W: std::fmt::Write,
491 {
492 for token_or_value in self.0.iter() {
493 match token_or_value {
494 TokenOrValue::Color(color) => {
495 color.to_css(dest)?;
496 }
497 TokenOrValue::UnresolvedColor(color) => {
498 color.to_css(dest, is_custom_property)?;
499 }
500 TokenOrValue::Url(url) => {
501 if dest.dependencies.is_some() && is_custom_property && !url.is_absolute() {
502 return Err(dest.error(
503 PrinterErrorKind::AmbiguousUrlInCustomProperty {
504 url: url.url.as_ref().to_owned(),
505 },
506 url.loc,
507 ));
508 }
509 url.to_css(dest)?;
510 }
511 TokenOrValue::Var(var) => {
512 var.to_css(dest, is_custom_property)?;
513 }
514 TokenOrValue::Env(env) => {
515 env.to_css(dest, is_custom_property)?;
516 }
517 TokenOrValue::Function(f) => {
518 f.to_css(dest, is_custom_property)?;
519 }
520 TokenOrValue::Length(v) => {
521 let (value, unit) = v.to_unit_value();
523 serialize_dimension(value, unit, dest)?;
524 }
525 TokenOrValue::Angle(v) => {
526 v.to_css(dest)?;
527 }
528 TokenOrValue::Time(v) => {
529 v.to_css(dest)?;
530 }
531 TokenOrValue::Resolution(v) => {
532 v.to_css(dest)?;
533 }
534 TokenOrValue::DashedIdent(v) => {
535 v.to_css(dest)?;
536 }
537 TokenOrValue::AnimationName(v) => {
538 v.to_css(dest)?;
539 }
540 TokenOrValue::Token(token) => match token {
541 Token::Dimension { value, unit, .. } => {
542 serialize_dimension(*value, unit, dest)?;
543 }
544 Token::Number { value, .. } => {
545 value.to_css(dest)?;
546 }
547 _ => {
548 token.to_css(dest)?;
549 }
550 },
551 };
552 }
553
554 Ok(())
555 }
556
557 pub(crate) fn to_css_raw<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
558 where
559 W: std::fmt::Write,
560 {
561 for token_or_value in &self.0 {
562 match token_or_value {
563 TokenOrValue::Token(token) => {
564 token.to_css(dest)?;
565 }
566 _ => {
567 return Err(PrinterError {
568 kind: PrinterErrorKind::FmtError,
569 loc: None,
570 })
571 }
572 }
573 }
574
575 Ok(())
576 }
577
578 pub(crate) fn starts_with_whitespace(&self) -> bool {
579 matches!(self.0.get(0), Some(TokenOrValue::Token(Token::WhiteSpace(_))))
580 }
581}
582
583#[derive(Debug, Clone, PartialEq)]
586#[cfg_attr(feature = "visitor", derive(Visit))]
587#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
588#[cfg_attr(
589 feature = "serde",
590 derive(serde::Serialize, serde::Deserialize),
591 serde(tag = "type", rename_all = "kebab-case")
592)]
593#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
594pub enum Token<'a> {
595 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
597 Ident(#[cfg_attr(feature = "serde", serde(borrow))] CowArcStr<'a>),
598
599 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
603 AtKeyword(CowArcStr<'a>),
604
605 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
609 Hash(CowArcStr<'a>),
610
611 #[cfg_attr(feature = "serde", serde(rename = "id-hash", with = "ValueWrapper::<CowArcStr>"))]
615 IDHash(CowArcStr<'a>), #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
621 String(CowArcStr<'a>),
622
623 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
628 UnquotedUrl(CowArcStr<'a>),
629
630 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<char>"))]
632 Delim(char),
633
634 Number {
636 #[cfg_attr(feature = "serde", serde(skip))]
640 has_sign: bool,
641
642 value: f32,
644
645 #[cfg_attr(feature = "serde", serde(skip))]
647 int_value: Option<i32>,
648 },
649
650 Percentage {
652 #[cfg_attr(feature = "serde", serde(skip))]
654 has_sign: bool,
655
656 #[cfg_attr(feature = "serde", serde(rename = "value"))]
658 unit_value: f32,
659
660 #[cfg_attr(feature = "serde", serde(skip))]
663 int_value: Option<i32>,
664 },
665
666 Dimension {
668 #[cfg_attr(feature = "serde", serde(skip))]
672 has_sign: bool,
673
674 value: f32,
676
677 #[cfg_attr(feature = "serde", serde(skip))]
679 int_value: Option<i32>,
680
681 unit: CowArcStr<'a>,
683 },
684
685 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
687 WhiteSpace(CowArcStr<'a>),
688
689 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
696 Comment(CowArcStr<'a>),
697
698 Colon, Semicolon, Comma, IncludeMatch,
709
710 DashMatch,
712
713 PrefixMatch,
715
716 SuffixMatch,
718
719 SubstringMatch,
721
722 #[cfg_attr(feature = "serde", serde(rename = "cdo"))]
724 CDO,
725
726 #[cfg_attr(feature = "serde", serde(rename = "cdc"))]
728 CDC,
729
730 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
734 Function(CowArcStr<'a>),
735
736 ParenthesisBlock,
738
739 SquareBracketBlock,
741
742 CurlyBracketBlock,
744
745 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
749 BadUrl(CowArcStr<'a>),
750
751 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
755 BadString(CowArcStr<'a>),
756
757 CloseParenthesis,
762
763 CloseSquareBracket,
768
769 CloseCurlyBracket,
774}
775
776impl<'a> From<&cssparser::Token<'a>> for Token<'a> {
777 #[inline]
778 fn from(t: &cssparser::Token<'a>) -> Token<'a> {
779 match t {
780 cssparser::Token::Ident(x) => Token::Ident(x.into()),
781 cssparser::Token::AtKeyword(x) => Token::AtKeyword(x.into()),
782 cssparser::Token::Hash(x) => Token::Hash(x.into()),
783 cssparser::Token::IDHash(x) => Token::IDHash(x.into()),
784 cssparser::Token::QuotedString(x) => Token::String(x.into()),
785 cssparser::Token::UnquotedUrl(x) => Token::UnquotedUrl(x.into()),
786 cssparser::Token::Function(x) => Token::Function(x.into()),
787 cssparser::Token::BadUrl(x) => Token::BadUrl(x.into()),
788 cssparser::Token::BadString(x) => Token::BadString(x.into()),
789 cssparser::Token::Delim(c) => Token::Delim(*c),
790 cssparser::Token::Number {
791 has_sign,
792 value,
793 int_value,
794 } => Token::Number {
795 has_sign: *has_sign,
796 value: *value,
797 int_value: *int_value,
798 },
799 cssparser::Token::Dimension {
800 has_sign,
801 value,
802 int_value,
803 unit,
804 } => Token::Dimension {
805 has_sign: *has_sign,
806 value: *value,
807 int_value: *int_value,
808 unit: unit.into(),
809 },
810 cssparser::Token::Percentage {
811 has_sign,
812 unit_value,
813 int_value,
814 } => Token::Percentage {
815 has_sign: *has_sign,
816 unit_value: *unit_value,
817 int_value: *int_value,
818 },
819 cssparser::Token::WhiteSpace(w) => Token::WhiteSpace((*w).into()),
820 cssparser::Token::Comment(c) => Token::Comment((*c).into()),
821 cssparser::Token::Colon => Token::Colon,
822 cssparser::Token::Semicolon => Token::Semicolon,
823 cssparser::Token::Comma => Token::Comma,
824 cssparser::Token::IncludeMatch => Token::IncludeMatch,
825 cssparser::Token::DashMatch => Token::DashMatch,
826 cssparser::Token::PrefixMatch => Token::PrefixMatch,
827 cssparser::Token::SuffixMatch => Token::SuffixMatch,
828 cssparser::Token::SubstringMatch => Token::SubstringMatch,
829 cssparser::Token::CDO => Token::CDO,
830 cssparser::Token::CDC => Token::CDC,
831 cssparser::Token::ParenthesisBlock => Token::ParenthesisBlock,
832 cssparser::Token::SquareBracketBlock => Token::SquareBracketBlock,
833 cssparser::Token::CurlyBracketBlock => Token::CurlyBracketBlock,
834 cssparser::Token::CloseParenthesis => Token::CloseParenthesis,
835 cssparser::Token::CloseSquareBracket => Token::CloseSquareBracket,
836 cssparser::Token::CloseCurlyBracket => Token::CloseCurlyBracket,
837 }
838 }
839}
840
841impl<'a> ToCss for Token<'a> {
842 #[inline]
843 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
844 where
845 W: std::fmt::Write,
846 {
847 use cssparser::ToCss;
848 match self {
849 Token::Ident(x) => cssparser::Token::Ident(x.as_ref().into()).to_css(dest)?,
850 Token::AtKeyword(x) => cssparser::Token::AtKeyword(x.as_ref().into()).to_css(dest)?,
851 Token::Hash(x) => cssparser::Token::Hash(x.as_ref().into()).to_css(dest)?,
852 Token::IDHash(x) => cssparser::Token::IDHash(x.as_ref().into()).to_css(dest)?,
853 Token::String(x) => cssparser::Token::QuotedString(x.as_ref().into()).to_css(dest)?,
854 Token::UnquotedUrl(x) => cssparser::Token::UnquotedUrl(x.as_ref().into()).to_css(dest)?,
855 Token::Function(x) => cssparser::Token::Function(x.as_ref().into()).to_css(dest)?,
856 Token::BadUrl(x) => cssparser::Token::BadUrl(x.as_ref().into()).to_css(dest)?,
857 Token::BadString(x) => cssparser::Token::BadString(x.as_ref().into()).to_css(dest)?,
858 Token::Delim(c) => cssparser::Token::Delim(*c).to_css(dest)?,
859 Token::Number {
860 has_sign,
861 value,
862 int_value,
863 } => cssparser::Token::Number {
864 has_sign: *has_sign,
865 value: *value,
866 int_value: *int_value,
867 }
868 .to_css(dest)?,
869 Token::Dimension {
870 has_sign,
871 value,
872 int_value,
873 unit,
874 } => cssparser::Token::Dimension {
875 has_sign: *has_sign,
876 value: *value,
877 int_value: *int_value,
878 unit: unit.as_ref().into(),
879 }
880 .to_css(dest)?,
881 Token::Percentage {
882 has_sign,
883 unit_value,
884 int_value,
885 } => cssparser::Token::Percentage {
886 has_sign: *has_sign,
887 unit_value: *unit_value,
888 int_value: *int_value,
889 }
890 .to_css(dest)?,
891 Token::WhiteSpace(w) => {
892 if dest.minify {
893 dest.write_char(' ')?;
894 } else {
895 dest.write_str(&w)?;
896 }
897 }
898 Token::Comment(c) => {
899 if !dest.minify {
900 cssparser::Token::Comment(c).to_css(dest)?;
901 }
902 }
903 Token::Colon => cssparser::Token::Colon.to_css(dest)?,
904 Token::Semicolon => cssparser::Token::Semicolon.to_css(dest)?,
905 Token::Comma => cssparser::Token::Comma.to_css(dest)?,
906 Token::IncludeMatch => cssparser::Token::IncludeMatch.to_css(dest)?,
907 Token::DashMatch => cssparser::Token::DashMatch.to_css(dest)?,
908 Token::PrefixMatch => cssparser::Token::PrefixMatch.to_css(dest)?,
909 Token::SuffixMatch => cssparser::Token::SuffixMatch.to_css(dest)?,
910 Token::SubstringMatch => cssparser::Token::SubstringMatch.to_css(dest)?,
911 Token::CDO => cssparser::Token::CDO.to_css(dest)?,
912 Token::CDC => cssparser::Token::CDC.to_css(dest)?,
913 Token::ParenthesisBlock => cssparser::Token::ParenthesisBlock.to_css(dest)?,
914 Token::SquareBracketBlock => cssparser::Token::SquareBracketBlock.to_css(dest)?,
915 Token::CurlyBracketBlock => cssparser::Token::CurlyBracketBlock.to_css(dest)?,
916 Token::CloseParenthesis => cssparser::Token::CloseParenthesis.to_css(dest)?,
917 Token::CloseSquareBracket => cssparser::Token::CloseSquareBracket.to_css(dest)?,
918 Token::CloseCurlyBracket => cssparser::Token::CloseCurlyBracket.to_css(dest)?,
919 }
920
921 Ok(())
922 }
923}
924
925impl<'a> Eq for Token<'a> {}
926
927impl<'a> std::hash::Hash for Token<'a> {
928 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
929 let tag = std::mem::discriminant(self);
930 tag.hash(state);
931 match self {
932 Token::Ident(x) => x.hash(state),
933 Token::AtKeyword(x) => x.hash(state),
934 Token::Hash(x) => x.hash(state),
935 Token::IDHash(x) => x.hash(state),
936 Token::String(x) => x.hash(state),
937 Token::UnquotedUrl(x) => x.hash(state),
938 Token::Function(x) => x.hash(state),
939 Token::BadUrl(x) => x.hash(state),
940 Token::BadString(x) => x.hash(state),
941 Token::Delim(x) => x.hash(state),
942 Token::Number {
943 has_sign,
944 value,
945 int_value,
946 } => {
947 has_sign.hash(state);
948 integer_decode(*value).hash(state);
949 int_value.hash(state);
950 }
951 Token::Dimension {
952 has_sign,
953 value,
954 int_value,
955 unit,
956 } => {
957 has_sign.hash(state);
958 integer_decode(*value).hash(state);
959 int_value.hash(state);
960 unit.hash(state);
961 }
962 Token::Percentage {
963 has_sign,
964 unit_value,
965 int_value,
966 } => {
967 has_sign.hash(state);
968 integer_decode(*unit_value).hash(state);
969 int_value.hash(state);
970 }
971 Token::WhiteSpace(w) => w.hash(state),
972 Token::Comment(c) => c.hash(state),
973 Token::Colon
974 | Token::Semicolon
975 | Token::Comma
976 | Token::IncludeMatch
977 | Token::DashMatch
978 | Token::PrefixMatch
979 | Token::SuffixMatch
980 | Token::SubstringMatch
981 | Token::CDO
982 | Token::CDC
983 | Token::ParenthesisBlock
984 | Token::SquareBracketBlock
985 | Token::CurlyBracketBlock
986 | Token::CloseParenthesis
987 | Token::CloseSquareBracket
988 | Token::CloseCurlyBracket => {}
989 }
990 }
991}
992
993fn integer_decode(v: f32) -> (u32, i16, i8) {
996 let bits: u32 = unsafe { std::mem::transmute(v) };
997 let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
998 let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
999 let mantissa = if exponent == 0 {
1000 (bits & 0x7fffff) << 1
1001 } else {
1002 (bits & 0x7fffff) | 0x800000
1003 };
1004 exponent -= 127 + 23;
1006 (mantissa, exponent, sign)
1007}
1008
1009impl<'i> TokenList<'i> {
1010 pub(crate) fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
1011 let mut fallbacks = ColorFallbackKind::empty();
1012 for token in &self.0 {
1013 match token {
1014 TokenOrValue::Color(color) => {
1015 fallbacks |= color.get_possible_fallbacks(targets);
1016 }
1017 TokenOrValue::Function(f) => {
1018 fallbacks |= f.arguments.get_necessary_fallbacks(targets);
1019 }
1020 TokenOrValue::Var(v) => {
1021 if let Some(fallback) = &v.fallback {
1022 fallbacks |= fallback.get_necessary_fallbacks(targets);
1023 }
1024 }
1025 TokenOrValue::Env(v) => {
1026 if let Some(fallback) = &v.fallback {
1027 fallbacks |= fallback.get_necessary_fallbacks(targets);
1028 }
1029 }
1030 _ => {}
1031 }
1032 }
1033
1034 fallbacks
1035 }
1036
1037 pub(crate) fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1038 let tokens = self
1039 .0
1040 .iter()
1041 .map(|token| match token {
1042 TokenOrValue::Color(color) => TokenOrValue::Color(color.get_fallback(kind)),
1043 TokenOrValue::Function(f) => TokenOrValue::Function(f.get_fallback(kind)),
1044 TokenOrValue::Var(v) => TokenOrValue::Var(v.get_fallback(kind)),
1045 TokenOrValue::Env(e) => TokenOrValue::Env(e.get_fallback(kind)),
1046 _ => token.clone(),
1047 })
1048 .collect();
1049 TokenList(tokens)
1050 }
1051
1052 pub(crate) fn get_fallbacks(&mut self, targets: Targets) -> Vec<(SupportsCondition<'i>, Self)> {
1053 let mut fallbacks = self.get_necessary_fallbacks(targets);
1056 let lowest_fallback = fallbacks.lowest();
1057 fallbacks.remove(lowest_fallback);
1058
1059 let mut res = Vec::new();
1060 if fallbacks.contains(ColorFallbackKind::P3) {
1061 res.push((
1062 ColorFallbackKind::P3.supports_condition(),
1063 self.get_fallback(ColorFallbackKind::P3),
1064 ));
1065 }
1066
1067 if fallbacks.contains(ColorFallbackKind::LAB) {
1068 res.push((
1069 ColorFallbackKind::LAB.supports_condition(),
1070 self.get_fallback(ColorFallbackKind::LAB),
1071 ));
1072 }
1073
1074 if !lowest_fallback.is_empty() {
1075 for token in self.0.iter_mut() {
1076 match token {
1077 TokenOrValue::Color(color) => {
1078 *color = color.get_fallback(lowest_fallback);
1079 }
1080 TokenOrValue::Function(f) => *f = f.get_fallback(lowest_fallback),
1081 TokenOrValue::Var(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),
1082 TokenOrValue::Env(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),
1083 _ => {}
1084 }
1085 }
1086 }
1087
1088 res
1089 }
1090
1091 pub(crate) fn get_features(&self) -> Features {
1092 let mut features = Features::empty();
1093 for token in &self.0 {
1094 match token {
1095 TokenOrValue::Color(color) => {
1096 features |= color.get_features();
1097 }
1098 TokenOrValue::UnresolvedColor(unresolved_color) => {
1099 features |= Features::SpaceSeparatedColorNotation;
1100 match unresolved_color {
1101 UnresolvedColor::LightDark { light, dark } => {
1102 features |= Features::LightDark;
1103 features |= light.get_features();
1104 features |= dark.get_features();
1105 }
1106 _ => {}
1107 }
1108 }
1109 TokenOrValue::Function(f) => {
1110 features |= f.arguments.get_features();
1111 }
1112 TokenOrValue::Var(v) => {
1113 if let Some(fallback) = &v.fallback {
1114 features |= fallback.get_features();
1115 }
1116 }
1117 TokenOrValue::Env(v) => {
1118 if let Some(fallback) = &v.fallback {
1119 features |= fallback.get_features();
1120 }
1121 }
1122 _ => {}
1123 }
1124 }
1125
1126 features
1127 }
1128
1129 #[cfg(feature = "substitute_variables")]
1131 #[cfg_attr(docsrs, doc(cfg(feature = "substitute_variables")))]
1132 pub fn substitute_variables(&mut self, vars: &std::collections::HashMap<&str, TokenList<'i>>) {
1133 self.visit(&mut VarInliner { vars }).unwrap()
1134 }
1135}
1136
1137#[cfg(feature = "substitute_variables")]
1138struct VarInliner<'a, 'i> {
1139 vars: &'a std::collections::HashMap<&'a str, TokenList<'i>>,
1140}
1141
1142#[cfg(feature = "substitute_variables")]
1143impl<'a, 'i> crate::visitor::Visitor<'i> for VarInliner<'a, 'i> {
1144 type Error = std::convert::Infallible;
1145
1146 fn visit_types(&self) -> crate::visitor::VisitTypes {
1147 crate::visit_types!(TOKENS | VARIABLES)
1148 }
1149
1150 fn visit_token_list(&mut self, tokens: &mut TokenList<'i>) -> Result<(), Self::Error> {
1151 let mut i = 0;
1152 let mut seen = std::collections::HashSet::new();
1153 while i < tokens.0.len() {
1154 let token = &mut tokens.0[i];
1155 token.visit(self).unwrap();
1156 if let TokenOrValue::Var(var) = token {
1157 if let Some(value) = self.vars.get(var.name.ident.0.as_ref()) {
1158 if seen.insert(var.name.ident.0.clone()) {
1160 tokens.0.splice(i..i + 1, value.0.iter().cloned());
1161 continue;
1163 }
1164 } else if let Some(fallback) = &var.fallback {
1165 let fallback = fallback.0.clone();
1166 if seen.insert(var.name.ident.0.clone()) {
1167 tokens.0.splice(i..i + 1, fallback.into_iter());
1168 continue;
1169 }
1170 }
1171 }
1172 seen.clear();
1173 i += 1;
1174 }
1175 Ok(())
1176 }
1177}
1178
1179#[derive(Debug, Clone, PartialEq)]
1181#[cfg_attr(feature = "visitor", derive(Visit))]
1182#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1183#[cfg_attr(feature = "visitor", visit(visit_variable, VARIABLES))]
1184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1185#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1186pub struct Variable<'i> {
1187 #[cfg_attr(feature = "serde", serde(borrow))]
1189 pub name: DashedIdentReference<'i>,
1190 pub fallback: Option<TokenList<'i>>,
1192}
1193
1194impl<'i> Variable<'i> {
1195 fn parse<'t>(
1196 input: &mut Parser<'i, 't>,
1197 options: &ParserOptions<'_, 'i>,
1198 depth: usize,
1199 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1200 let name = DashedIdentReference::parse_with_options(input, options)?;
1201
1202 let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
1203 Some(TokenList::parse(input, options, depth)?)
1204 } else {
1205 None
1206 };
1207
1208 Ok(Variable { name, fallback })
1209 }
1210
1211 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1212 where
1213 W: std::fmt::Write,
1214 {
1215 dest.write_str("var(")?;
1216 self.name.to_css(dest)?;
1217 if let Some(fallback) = &self.fallback {
1218 dest.delim(',', false)?;
1219 fallback.to_css(dest, is_custom_property)?;
1220 }
1221 dest.write_char(')')
1222 }
1223
1224 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1225 Variable {
1226 name: self.name.clone(),
1227 fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
1228 }
1229 }
1230}
1231
1232#[derive(Debug, Clone, PartialEq)]
1234#[cfg_attr(
1235 feature = "visitor",
1236 derive(Visit),
1237 visit(visit_environment_variable, ENVIRONMENT_VARIABLES)
1238)]
1239#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1240#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1241#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1242pub struct EnvironmentVariable<'i> {
1243 #[cfg_attr(feature = "serde", serde(borrow))]
1245 pub name: EnvironmentVariableName<'i>,
1246 #[cfg_attr(feature = "serde", serde(default))]
1248 pub indices: Vec<CSSInteger>,
1249 pub fallback: Option<TokenList<'i>>,
1251}
1252
1253#[derive(Debug, Clone, PartialEq)]
1255#[cfg_attr(feature = "visitor", derive(Visit))]
1256#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1257#[cfg_attr(
1258 feature = "serde",
1259 derive(serde::Serialize, serde::Deserialize),
1260 serde(tag = "type", rename_all = "lowercase")
1261)]
1262#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1263pub enum EnvironmentVariableName<'i> {
1264 #[cfg_attr(
1266 feature = "serde",
1267 serde(with = "crate::serialization::ValueWrapper::<UAEnvironmentVariable>")
1268 )]
1269 UA(UAEnvironmentVariable),
1270 #[cfg_attr(feature = "serde", serde(borrow))]
1272 Custom(DashedIdentReference<'i>),
1273 #[cfg_attr(feature = "serde", serde(with = "crate::serialization::ValueWrapper::<CustomIdent>"))]
1275 Unknown(CustomIdent<'i>),
1276}
1277
1278enum_property! {
1279 pub enum UAEnvironmentVariable {
1281 SafeAreaInsetTop,
1283 SafeAreaInsetRight,
1285 SafeAreaInsetBottom,
1287 SafeAreaInsetLeft,
1289 ViewportSegmentWidth,
1291 ViewportSegmentHeight,
1293 ViewportSegmentTop,
1295 ViewportSegmentLeft,
1297 ViewportSegmentBottom,
1299 ViewportSegmentRight,
1301 }
1302}
1303
1304impl<'i> EnvironmentVariableName<'i> {
1305 pub fn name(&self) -> &str {
1307 match self {
1308 EnvironmentVariableName::UA(ua) => ua.as_str(),
1309 EnvironmentVariableName::Custom(c) => c.ident.as_ref(),
1310 EnvironmentVariableName::Unknown(u) => u.0.as_ref(),
1311 }
1312 }
1313}
1314
1315impl<'i> Parse<'i> for EnvironmentVariableName<'i> {
1316 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1317 if let Ok(ua) = input.try_parse(UAEnvironmentVariable::parse) {
1318 return Ok(EnvironmentVariableName::UA(ua));
1319 }
1320
1321 if let Ok(dashed) =
1322 input.try_parse(|input| DashedIdentReference::parse_with_options(input, &ParserOptions::default()))
1323 {
1324 return Ok(EnvironmentVariableName::Custom(dashed));
1325 }
1326
1327 let ident = CustomIdent::parse(input)?;
1328 return Ok(EnvironmentVariableName::Unknown(ident));
1329 }
1330}
1331
1332impl<'i> ToCss for EnvironmentVariableName<'i> {
1333 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1334 where
1335 W: std::fmt::Write,
1336 {
1337 match self {
1338 EnvironmentVariableName::UA(ua) => ua.to_css(dest),
1339 EnvironmentVariableName::Custom(custom) => custom.to_css(dest),
1340 EnvironmentVariableName::Unknown(unknown) => unknown.to_css(dest),
1341 }
1342 }
1343}
1344
1345impl<'i> EnvironmentVariable<'i> {
1346 pub(crate) fn parse<'t>(
1347 input: &mut Parser<'i, 't>,
1348 options: &ParserOptions<'_, 'i>,
1349 depth: usize,
1350 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1351 input.expect_function_matching("env")?;
1352 input.parse_nested_block(|input| Self::parse_nested(input, options, depth))
1353 }
1354
1355 pub(crate) fn parse_nested<'t>(
1356 input: &mut Parser<'i, 't>,
1357 options: &ParserOptions<'_, 'i>,
1358 depth: usize,
1359 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1360 let name = EnvironmentVariableName::parse(input)?;
1361 let mut indices = Vec::new();
1362 while let Ok(index) = input.try_parse(CSSInteger::parse) {
1363 indices.push(index);
1364 }
1365
1366 let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
1367 Some(TokenList::parse(input, options, depth + 1)?)
1368 } else {
1369 None
1370 };
1371
1372 Ok(EnvironmentVariable {
1373 name,
1374 indices,
1375 fallback,
1376 })
1377 }
1378
1379 pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1380 where
1381 W: std::fmt::Write,
1382 {
1383 dest.write_str("env(")?;
1384 self.name.to_css(dest)?;
1385
1386 for item in &self.indices {
1387 dest.write_char(' ')?;
1388 item.to_css(dest)?;
1389 }
1390
1391 if let Some(fallback) = &self.fallback {
1392 dest.delim(',', false)?;
1393 fallback.to_css(dest, is_custom_property)?;
1394 }
1395 dest.write_char(')')
1396 }
1397
1398 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1399 EnvironmentVariable {
1400 name: self.name.clone(),
1401 indices: self.indices.clone(),
1402 fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
1403 }
1404 }
1405}
1406
1407#[derive(Debug, Clone, PartialEq)]
1409#[cfg_attr(feature = "visitor", derive(Visit))]
1410#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1411#[cfg_attr(feature = "visitor", visit(visit_function, FUNCTIONS))]
1412#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1413#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1414pub struct Function<'i> {
1415 #[cfg_attr(feature = "serde", serde(borrow))]
1417 pub name: Ident<'i>,
1418 pub arguments: TokenList<'i>,
1420}
1421
1422impl<'i> Function<'i> {
1423 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1424 where
1425 W: std::fmt::Write,
1426 {
1427 self.name.to_css(dest)?;
1428 dest.write_char('(')?;
1429 self.arguments.to_css(dest, is_custom_property)?;
1430 dest.write_char(')')
1431 }
1432
1433 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1434 Function {
1435 name: self.name.clone(),
1436 arguments: self.arguments.get_fallback(kind),
1437 }
1438 }
1439}
1440
1441#[derive(Debug, Clone, PartialEq)]
1446#[cfg_attr(feature = "visitor", derive(Visit))]
1447#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1448#[cfg_attr(
1449 feature = "serde",
1450 derive(serde::Serialize, serde::Deserialize),
1451 serde(tag = "type", rename_all = "lowercase")
1452)]
1453#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1454pub enum UnresolvedColor<'i> {
1455 RGB {
1457 r: f32,
1459 g: f32,
1461 b: f32,
1463 #[cfg_attr(feature = "serde", serde(borrow))]
1465 alpha: TokenList<'i>,
1466 },
1467 HSL {
1469 h: f32,
1471 s: f32,
1473 l: f32,
1475 #[cfg_attr(feature = "serde", serde(borrow))]
1477 alpha: TokenList<'i>,
1478 },
1479 #[cfg_attr(feature = "serde", serde(rename = "light-dark"))]
1481 LightDark {
1482 light: TokenList<'i>,
1484 dark: TokenList<'i>,
1486 },
1487}
1488
1489impl<'i> LightDarkColor for UnresolvedColor<'i> {
1490 #[inline]
1491 fn light_dark(light: Self, dark: Self) -> Self {
1492 UnresolvedColor::LightDark {
1493 light: TokenList(vec![TokenOrValue::UnresolvedColor(light)]),
1494 dark: TokenList(vec![TokenOrValue::UnresolvedColor(dark)]),
1495 }
1496 }
1497}
1498
1499impl<'i> UnresolvedColor<'i> {
1500 fn parse<'t>(
1501 f: &CowArcStr<'i>,
1502 input: &mut Parser<'i, 't>,
1503 options: &ParserOptions<'_, 'i>,
1504 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1505 let mut parser = ComponentParser::new(false);
1506 match_ignore_ascii_case! { &*f,
1507 "rgb" => {
1508 input.parse_nested_block(|input| {
1509 parser.parse_relative::<RGB, _, _>(input, |input, parser| {
1510 let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
1511 if is_legacy {
1512 return Err(input.new_custom_error(ParserError::InvalidValue))
1513 }
1514 input.expect_delim('/')?;
1515 let alpha = TokenList::parse(input, options, 0)?;
1516 Ok(UnresolvedColor::RGB { r, g, b, alpha })
1517 })
1518 })
1519 },
1520 "hsl" => {
1521 input.parse_nested_block(|input| {
1522 parser.parse_relative::<HSL, _, _>(input, |input, parser| {
1523 let (h, s, l, is_legacy) = parse_hsl_hwb_components::<HSL>(input, parser, false)?;
1524 if is_legacy {
1525 return Err(input.new_custom_error(ParserError::InvalidValue))
1526 }
1527 input.expect_delim('/')?;
1528 let alpha = TokenList::parse(input, options, 0)?;
1529 Ok(UnresolvedColor::HSL { h, s, l, alpha })
1530 })
1531 })
1532 },
1533 "light-dark" => {
1534 input.parse_nested_block(|input| {
1535 let light = input.parse_until_before(Delimiter::Comma, |input|
1536 TokenList::parse(input, options, 0)
1537 )?;
1538 input.expect_comma()?;
1539 let dark = TokenList::parse(input, options, 0)?;
1540 Ok(UnresolvedColor::LightDark { light, dark })
1541 })
1542 },
1543 _ => Err(input.new_custom_error(ParserError::InvalidValue))
1544 }
1545 }
1546
1547 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1548 where
1549 W: std::fmt::Write,
1550 {
1551 match self {
1552 UnresolvedColor::RGB { r, g, b, alpha } => {
1553 if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {
1554 dest.write_str("rgba(")?;
1555 r.to_css(dest)?;
1556 dest.delim(',', false)?;
1557 g.to_css(dest)?;
1558 dest.delim(',', false)?;
1559 b.to_css(dest)?;
1560 dest.delim(',', false)?;
1561 alpha.to_css(dest, is_custom_property)?;
1562 dest.write_char(')')?;
1563 return Ok(());
1564 }
1565
1566 dest.write_str("rgb(")?;
1567 r.to_css(dest)?;
1568 dest.write_char(' ')?;
1569 g.to_css(dest)?;
1570 dest.write_char(' ')?;
1571 b.to_css(dest)?;
1572 dest.delim('/', true)?;
1573 alpha.to_css(dest, is_custom_property)?;
1574 dest.write_char(')')
1575 }
1576 UnresolvedColor::HSL { h, s, l, alpha } => {
1577 if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {
1578 dest.write_str("hsla(")?;
1579 h.to_css(dest)?;
1580 dest.delim(',', false)?;
1581 Percentage(*s / 100.0).to_css(dest)?;
1582 dest.delim(',', false)?;
1583 Percentage(*l / 100.0).to_css(dest)?;
1584 dest.delim(',', false)?;
1585 alpha.to_css(dest, is_custom_property)?;
1586 dest.write_char(')')?;
1587 return Ok(());
1588 }
1589
1590 dest.write_str("hsl(")?;
1591 h.to_css(dest)?;
1592 dest.write_char(' ')?;
1593 Percentage(*s / 100.0).to_css(dest)?;
1594 dest.write_char(' ')?;
1595 Percentage(*l / 100.0).to_css(dest)?;
1596 dest.delim('/', true)?;
1597 alpha.to_css(dest, is_custom_property)?;
1598 dest.write_char(')')
1599 }
1600 UnresolvedColor::LightDark { light, dark } => {
1601 if should_compile!(dest.targets.current, LightDark) {
1602 dest.write_str("var(--lightningcss-light")?;
1603 dest.delim(',', false)?;
1604 light.to_css(dest, is_custom_property)?;
1605 dest.write_char(')')?;
1606 dest.whitespace()?;
1607 dest.write_str("var(--lightningcss-dark")?;
1608 dest.delim(',', false)?;
1609 dark.to_css(dest, is_custom_property)?;
1610 return dest.write_char(')');
1611 }
1612
1613 dest.write_str("light-dark(")?;
1614 light.to_css(dest, is_custom_property)?;
1615 dest.delim(',', false)?;
1616 dark.to_css(dest, is_custom_property)?;
1617 dest.write_char(')')
1618 }
1619 }
1620 }
1621}