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, 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, RGBA, SRGB,
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 let mut last_is_delim = false;
374 let mut last_is_whitespace = false;
375 loop {
376 let state = input.state();
377 match input.next_including_whitespace_and_comments() {
378 Ok(&cssparser::Token::WhiteSpace(..)) | Ok(&cssparser::Token::Comment(..)) => {
379 if !last_is_delim {
382 tokens.push(Token::WhiteSpace(" ".into()).into());
383 last_is_whitespace = true;
384 }
385 }
386 Ok(&cssparser::Token::Function(ref f)) => {
387 let f = f.into();
389 if let Some(color) = try_parse_color_token(&f, &state, input) {
390 tokens.push(TokenOrValue::Color(color));
391 last_is_delim = false;
392 last_is_whitespace = false;
393 } else if let Ok(color) = input.try_parse(|input| UnresolvedColor::parse(&f, input, options)) {
394 tokens.push(TokenOrValue::UnresolvedColor(color));
395 last_is_delim = true;
396 last_is_whitespace = false;
397 } else if f == "url" {
398 input.reset(&state);
399 tokens.push(TokenOrValue::Url(Url::parse(input)?));
400 last_is_delim = false;
401 last_is_whitespace = false;
402 } else if f == "var" {
403 let var = input.parse_nested_block(|input| {
404 let var = Variable::parse(input, options, depth + 1)?;
405 Ok(TokenOrValue::Var(var))
406 })?;
407 tokens.push(var);
408 last_is_delim = true;
409 last_is_whitespace = false;
410 } else if f == "env" {
411 let env = input.parse_nested_block(|input| {
412 let env = EnvironmentVariable::parse_nested(input, options, depth + 1)?;
413 Ok(TokenOrValue::Env(env))
414 })?;
415 tokens.push(env);
416 last_is_delim = true;
417 last_is_whitespace = false;
418 } else {
419 let arguments = input.parse_nested_block(|input| TokenList::parse(input, options, depth + 1))?;
420 tokens.push(TokenOrValue::Function(Function {
421 name: Ident(f),
422 arguments,
423 }));
424 last_is_delim = true; last_is_whitespace = false;
426 }
427 }
428 Ok(&cssparser::Token::Hash(ref h)) | Ok(&cssparser::Token::IDHash(ref h)) => {
429 if let Ok((r, g, b, a)) = parse_hash_color(h.as_bytes()) {
430 tokens.push(TokenOrValue::Color(CssColor::RGBA(RGBA::new(r, g, b, a))));
431 } else {
432 tokens.push(Token::Hash(h.into()).into());
433 }
434 last_is_delim = false;
435 last_is_whitespace = false;
436 }
437 Ok(&cssparser::Token::UnquotedUrl(_)) => {
438 input.reset(&state);
439 tokens.push(TokenOrValue::Url(Url::parse(input)?));
440 last_is_delim = false;
441 last_is_whitespace = false;
442 }
443 Ok(&cssparser::Token::Ident(ref name)) if name.starts_with("--") => {
444 tokens.push(TokenOrValue::DashedIdent(name.into()));
445 last_is_delim = false;
446 last_is_whitespace = false;
447 }
448 Ok(token @ &cssparser::Token::ParenthesisBlock)
449 | Ok(token @ &cssparser::Token::SquareBracketBlock)
450 | Ok(token @ &cssparser::Token::CurlyBracketBlock) => {
451 tokens.push(Token::from(token).into());
452 let closing_delimiter = match token {
453 cssparser::Token::ParenthesisBlock => Token::CloseParenthesis,
454 cssparser::Token::SquareBracketBlock => Token::CloseSquareBracket,
455 cssparser::Token::CurlyBracketBlock => Token::CloseCurlyBracket,
456 _ => unreachable!(),
457 };
458
459 input.parse_nested_block(|input| TokenList::parse_into(input, tokens, options, depth + 1))?;
460
461 tokens.push(closing_delimiter.into());
462 last_is_delim = true; last_is_whitespace = false;
464 }
465 Ok(token @ cssparser::Token::Dimension { .. }) => {
466 let value = if let Ok(length) = LengthValue::try_from(token) {
467 TokenOrValue::Length(length)
468 } else if let Ok(angle) = Angle::try_from(token) {
469 TokenOrValue::Angle(angle)
470 } else if let Ok(time) = Time::try_from(token) {
471 TokenOrValue::Time(time)
472 } else if let Ok(resolution) = Resolution::try_from(token) {
473 TokenOrValue::Resolution(resolution)
474 } else {
475 TokenOrValue::Token(token.into())
476 };
477 tokens.push(value);
478 last_is_delim = false;
479 last_is_whitespace = false;
480 }
481 Ok(token) if token.is_parse_error() => {
482 return Err(ParseError {
483 kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token.clone())),
484 location: state.source_location(),
485 })
486 }
487 Ok(token) => {
488 last_is_delim = matches!(token, cssparser::Token::Delim(_) | cssparser::Token::Comma);
489
490 if last_is_delim && last_is_whitespace {
493 let last = tokens.last_mut().unwrap();
494 *last = Token::from(token).into();
495 } else {
496 tokens.push(Token::from(token).into());
497 }
498
499 last_is_whitespace = false;
500 }
501 Err(_) => break,
502 }
503 }
504
505 Ok(())
506 }
507}
508
509#[inline]
510fn try_parse_color_token<'i, 't>(
511 f: &CowArcStr<'i>,
512 state: &ParserState,
513 input: &mut Parser<'i, 't>,
514) -> Option<CssColor> {
515 match_ignore_ascii_case! { &*f,
516 "rgb" | "rgba" | "hsl" | "hsla" | "hwb" | "lab" | "lch" | "oklab" | "oklch" | "color" | "color-mix" | "light-dark" => {
517 let s = input.state();
518 input.reset(&state);
519 if let Ok(color) = CssColor::parse(input) {
520 return Some(color)
521 }
522 input.reset(&s);
523 },
524 _ => {}
525 }
526
527 None
528}
529
530impl<'i> TokenList<'i> {
531 pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
532 where
533 W: std::fmt::Write,
534 {
535 if !dest.minify && self.0.len() == 1 && matches!(self.0.first(), Some(token) if token.is_whitespace()) {
536 return Ok(());
537 }
538
539 let mut has_whitespace = false;
540 for (i, token_or_value) in self.0.iter().enumerate() {
541 has_whitespace = match token_or_value {
542 TokenOrValue::Color(color) => {
543 color.to_css(dest)?;
544 false
545 }
546 TokenOrValue::UnresolvedColor(color) => {
547 color.to_css(dest, is_custom_property)?;
548 false
549 }
550 TokenOrValue::Url(url) => {
551 if dest.dependencies.is_some() && is_custom_property && !url.is_absolute() {
552 return Err(dest.error(
553 PrinterErrorKind::AmbiguousUrlInCustomProperty {
554 url: url.url.as_ref().to_owned(),
555 },
556 url.loc,
557 ));
558 }
559 url.to_css(dest)?;
560 false
561 }
562 TokenOrValue::Var(var) => {
563 var.to_css(dest, is_custom_property)?;
564 self.write_whitespace_if_needed(i, dest)?
565 }
566 TokenOrValue::Env(env) => {
567 env.to_css(dest, is_custom_property)?;
568 self.write_whitespace_if_needed(i, dest)?
569 }
570 TokenOrValue::Function(f) => {
571 f.to_css(dest, is_custom_property)?;
572 self.write_whitespace_if_needed(i, dest)?
573 }
574 TokenOrValue::Length(v) => {
575 let (value, unit) = v.to_unit_value();
577 serialize_dimension(value, unit, dest)?;
578 false
579 }
580 TokenOrValue::Angle(v) => {
581 v.to_css(dest)?;
582 false
583 }
584 TokenOrValue::Time(v) => {
585 v.to_css(dest)?;
586 false
587 }
588 TokenOrValue::Resolution(v) => {
589 v.to_css(dest)?;
590 false
591 }
592 TokenOrValue::DashedIdent(v) => {
593 v.to_css(dest)?;
594 false
595 }
596 TokenOrValue::AnimationName(v) => {
597 v.to_css(dest)?;
598 false
599 }
600 TokenOrValue::Token(token) => match token {
601 Token::Delim(d) => {
602 if *d == '+' || *d == '-' {
603 dest.write_char(' ')?;
604 dest.write_char(*d)?;
605 dest.write_char(' ')?;
606 } else {
607 let ws_before = !has_whitespace && (*d == '/' || *d == '*');
608 dest.delim(*d, ws_before)?;
609 }
610 true
611 }
612 Token::Comma => {
613 dest.delim(',', false)?;
614 true
615 }
616 Token::CloseParenthesis | Token::CloseSquareBracket | Token::CloseCurlyBracket => {
617 token.to_css(dest)?;
618 self.write_whitespace_if_needed(i, dest)?
619 }
620 Token::Dimension { value, unit, .. } => {
621 serialize_dimension(*value, unit, dest)?;
622 false
623 }
624 Token::Number { value, .. } => {
625 value.to_css(dest)?;
626 false
627 }
628 _ => {
629 token.to_css(dest)?;
630 matches!(token, Token::WhiteSpace(..))
631 }
632 },
633 };
634 }
635
636 Ok(())
637 }
638
639 pub(crate) fn to_css_raw<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
640 where
641 W: std::fmt::Write,
642 {
643 for token_or_value in &self.0 {
644 match token_or_value {
645 TokenOrValue::Token(token) => {
646 token.to_css(dest)?;
647 }
648 _ => {
649 return Err(PrinterError {
650 kind: PrinterErrorKind::FmtError,
651 loc: None,
652 })
653 }
654 }
655 }
656
657 Ok(())
658 }
659
660 #[inline]
661 fn write_whitespace_if_needed<W>(&self, i: usize, dest: &mut Printer<W>) -> Result<bool, PrinterError>
662 where
663 W: std::fmt::Write,
664 {
665 if !dest.minify
666 && i != self.0.len() - 1
667 && !matches!(
668 self.0[i + 1],
669 TokenOrValue::Token(Token::Comma) | TokenOrValue::Token(Token::CloseParenthesis)
670 )
671 {
672 dest.write_char(' ')?;
674 Ok(true)
675 } else {
676 Ok(false)
677 }
678 }
679}
680
681#[derive(Debug, Clone, PartialEq)]
684#[cfg_attr(feature = "visitor", derive(Visit))]
685#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
686#[cfg_attr(
687 feature = "serde",
688 derive(serde::Serialize, serde::Deserialize),
689 serde(tag = "type", rename_all = "kebab-case")
690)]
691#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
692pub enum Token<'a> {
693 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
695 Ident(#[cfg_attr(feature = "serde", serde(borrow))] CowArcStr<'a>),
696
697 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
701 AtKeyword(CowArcStr<'a>),
702
703 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
707 Hash(CowArcStr<'a>),
708
709 #[cfg_attr(feature = "serde", serde(rename = "id-hash", with = "ValueWrapper::<CowArcStr>"))]
713 IDHash(CowArcStr<'a>), #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
719 String(CowArcStr<'a>),
720
721 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
726 UnquotedUrl(CowArcStr<'a>),
727
728 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<char>"))]
730 Delim(char),
731
732 Number {
734 #[cfg_attr(feature = "serde", serde(skip))]
738 has_sign: bool,
739
740 value: f32,
742
743 #[cfg_attr(feature = "serde", serde(skip))]
745 int_value: Option<i32>,
746 },
747
748 Percentage {
750 #[cfg_attr(feature = "serde", serde(skip))]
752 has_sign: bool,
753
754 #[cfg_attr(feature = "serde", serde(rename = "value"))]
756 unit_value: f32,
757
758 #[cfg_attr(feature = "serde", serde(skip))]
761 int_value: Option<i32>,
762 },
763
764 Dimension {
766 #[cfg_attr(feature = "serde", serde(skip))]
770 has_sign: bool,
771
772 value: f32,
774
775 #[cfg_attr(feature = "serde", serde(skip))]
777 int_value: Option<i32>,
778
779 unit: CowArcStr<'a>,
781 },
782
783 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
785 WhiteSpace(CowArcStr<'a>),
786
787 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
794 Comment(CowArcStr<'a>),
795
796 Colon, Semicolon, Comma, IncludeMatch,
807
808 DashMatch,
810
811 PrefixMatch,
813
814 SuffixMatch,
816
817 SubstringMatch,
819
820 #[cfg_attr(feature = "serde", serde(rename = "cdo"))]
822 CDO,
823
824 #[cfg_attr(feature = "serde", serde(rename = "cdc"))]
826 CDC,
827
828 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
832 Function(CowArcStr<'a>),
833
834 ParenthesisBlock,
836
837 SquareBracketBlock,
839
840 CurlyBracketBlock,
842
843 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
847 BadUrl(CowArcStr<'a>),
848
849 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<CowArcStr>"))]
853 BadString(CowArcStr<'a>),
854
855 CloseParenthesis,
860
861 CloseSquareBracket,
866
867 CloseCurlyBracket,
872}
873
874impl<'a> From<&cssparser::Token<'a>> for Token<'a> {
875 #[inline]
876 fn from(t: &cssparser::Token<'a>) -> Token<'a> {
877 match t {
878 cssparser::Token::Ident(x) => Token::Ident(x.into()),
879 cssparser::Token::AtKeyword(x) => Token::AtKeyword(x.into()),
880 cssparser::Token::Hash(x) => Token::Hash(x.into()),
881 cssparser::Token::IDHash(x) => Token::IDHash(x.into()),
882 cssparser::Token::QuotedString(x) => Token::String(x.into()),
883 cssparser::Token::UnquotedUrl(x) => Token::UnquotedUrl(x.into()),
884 cssparser::Token::Function(x) => Token::Function(x.into()),
885 cssparser::Token::BadUrl(x) => Token::BadUrl(x.into()),
886 cssparser::Token::BadString(x) => Token::BadString(x.into()),
887 cssparser::Token::Delim(c) => Token::Delim(*c),
888 cssparser::Token::Number {
889 has_sign,
890 value,
891 int_value,
892 } => Token::Number {
893 has_sign: *has_sign,
894 value: *value,
895 int_value: *int_value,
896 },
897 cssparser::Token::Dimension {
898 has_sign,
899 value,
900 int_value,
901 unit,
902 } => Token::Dimension {
903 has_sign: *has_sign,
904 value: *value,
905 int_value: *int_value,
906 unit: unit.into(),
907 },
908 cssparser::Token::Percentage {
909 has_sign,
910 unit_value,
911 int_value,
912 } => Token::Percentage {
913 has_sign: *has_sign,
914 unit_value: *unit_value,
915 int_value: *int_value,
916 },
917 cssparser::Token::WhiteSpace(w) => Token::WhiteSpace((*w).into()),
918 cssparser::Token::Comment(c) => Token::Comment((*c).into()),
919 cssparser::Token::Colon => Token::Colon,
920 cssparser::Token::Semicolon => Token::Semicolon,
921 cssparser::Token::Comma => Token::Comma,
922 cssparser::Token::IncludeMatch => Token::IncludeMatch,
923 cssparser::Token::DashMatch => Token::DashMatch,
924 cssparser::Token::PrefixMatch => Token::PrefixMatch,
925 cssparser::Token::SuffixMatch => Token::SuffixMatch,
926 cssparser::Token::SubstringMatch => Token::SubstringMatch,
927 cssparser::Token::CDO => Token::CDO,
928 cssparser::Token::CDC => Token::CDC,
929 cssparser::Token::ParenthesisBlock => Token::ParenthesisBlock,
930 cssparser::Token::SquareBracketBlock => Token::SquareBracketBlock,
931 cssparser::Token::CurlyBracketBlock => Token::CurlyBracketBlock,
932 cssparser::Token::CloseParenthesis => Token::CloseParenthesis,
933 cssparser::Token::CloseSquareBracket => Token::CloseSquareBracket,
934 cssparser::Token::CloseCurlyBracket => Token::CloseCurlyBracket,
935 }
936 }
937}
938
939impl<'a> ToCss for Token<'a> {
940 #[inline]
941 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
942 where
943 W: std::fmt::Write,
944 {
945 use cssparser::ToCss;
946 match self {
947 Token::Ident(x) => cssparser::Token::Ident(x.as_ref().into()).to_css(dest)?,
948 Token::AtKeyword(x) => cssparser::Token::AtKeyword(x.as_ref().into()).to_css(dest)?,
949 Token::Hash(x) => cssparser::Token::Hash(x.as_ref().into()).to_css(dest)?,
950 Token::IDHash(x) => cssparser::Token::IDHash(x.as_ref().into()).to_css(dest)?,
951 Token::String(x) => cssparser::Token::QuotedString(x.as_ref().into()).to_css(dest)?,
952 Token::UnquotedUrl(x) => cssparser::Token::UnquotedUrl(x.as_ref().into()).to_css(dest)?,
953 Token::Function(x) => cssparser::Token::Function(x.as_ref().into()).to_css(dest)?,
954 Token::BadUrl(x) => cssparser::Token::BadUrl(x.as_ref().into()).to_css(dest)?,
955 Token::BadString(x) => cssparser::Token::BadString(x.as_ref().into()).to_css(dest)?,
956 Token::Delim(c) => cssparser::Token::Delim(*c).to_css(dest)?,
957 Token::Number {
958 has_sign,
959 value,
960 int_value,
961 } => cssparser::Token::Number {
962 has_sign: *has_sign,
963 value: *value,
964 int_value: *int_value,
965 }
966 .to_css(dest)?,
967 Token::Dimension {
968 has_sign,
969 value,
970 int_value,
971 unit,
972 } => cssparser::Token::Dimension {
973 has_sign: *has_sign,
974 value: *value,
975 int_value: *int_value,
976 unit: unit.as_ref().into(),
977 }
978 .to_css(dest)?,
979 Token::Percentage {
980 has_sign,
981 unit_value,
982 int_value,
983 } => cssparser::Token::Percentage {
984 has_sign: *has_sign,
985 unit_value: *unit_value,
986 int_value: *int_value,
987 }
988 .to_css(dest)?,
989 Token::WhiteSpace(w) => cssparser::Token::WhiteSpace(w).to_css(dest)?,
990 Token::Comment(c) => cssparser::Token::Comment(c).to_css(dest)?,
991 Token::Colon => cssparser::Token::Colon.to_css(dest)?,
992 Token::Semicolon => cssparser::Token::Semicolon.to_css(dest)?,
993 Token::Comma => cssparser::Token::Comma.to_css(dest)?,
994 Token::IncludeMatch => cssparser::Token::IncludeMatch.to_css(dest)?,
995 Token::DashMatch => cssparser::Token::DashMatch.to_css(dest)?,
996 Token::PrefixMatch => cssparser::Token::PrefixMatch.to_css(dest)?,
997 Token::SuffixMatch => cssparser::Token::SuffixMatch.to_css(dest)?,
998 Token::SubstringMatch => cssparser::Token::SubstringMatch.to_css(dest)?,
999 Token::CDO => cssparser::Token::CDO.to_css(dest)?,
1000 Token::CDC => cssparser::Token::CDC.to_css(dest)?,
1001 Token::ParenthesisBlock => cssparser::Token::ParenthesisBlock.to_css(dest)?,
1002 Token::SquareBracketBlock => cssparser::Token::SquareBracketBlock.to_css(dest)?,
1003 Token::CurlyBracketBlock => cssparser::Token::CurlyBracketBlock.to_css(dest)?,
1004 Token::CloseParenthesis => cssparser::Token::CloseParenthesis.to_css(dest)?,
1005 Token::CloseSquareBracket => cssparser::Token::CloseSquareBracket.to_css(dest)?,
1006 Token::CloseCurlyBracket => cssparser::Token::CloseCurlyBracket.to_css(dest)?,
1007 }
1008
1009 Ok(())
1010 }
1011}
1012
1013impl<'a> Eq for Token<'a> {}
1014
1015impl<'a> std::hash::Hash for Token<'a> {
1016 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1017 let tag = std::mem::discriminant(self);
1018 tag.hash(state);
1019 match self {
1020 Token::Ident(x) => x.hash(state),
1021 Token::AtKeyword(x) => x.hash(state),
1022 Token::Hash(x) => x.hash(state),
1023 Token::IDHash(x) => x.hash(state),
1024 Token::String(x) => x.hash(state),
1025 Token::UnquotedUrl(x) => x.hash(state),
1026 Token::Function(x) => x.hash(state),
1027 Token::BadUrl(x) => x.hash(state),
1028 Token::BadString(x) => x.hash(state),
1029 Token::Delim(x) => x.hash(state),
1030 Token::Number {
1031 has_sign,
1032 value,
1033 int_value,
1034 } => {
1035 has_sign.hash(state);
1036 integer_decode(*value).hash(state);
1037 int_value.hash(state);
1038 }
1039 Token::Dimension {
1040 has_sign,
1041 value,
1042 int_value,
1043 unit,
1044 } => {
1045 has_sign.hash(state);
1046 integer_decode(*value).hash(state);
1047 int_value.hash(state);
1048 unit.hash(state);
1049 }
1050 Token::Percentage {
1051 has_sign,
1052 unit_value,
1053 int_value,
1054 } => {
1055 has_sign.hash(state);
1056 integer_decode(*unit_value).hash(state);
1057 int_value.hash(state);
1058 }
1059 Token::WhiteSpace(w) => w.hash(state),
1060 Token::Comment(c) => c.hash(state),
1061 Token::Colon
1062 | Token::Semicolon
1063 | Token::Comma
1064 | Token::IncludeMatch
1065 | Token::DashMatch
1066 | Token::PrefixMatch
1067 | Token::SuffixMatch
1068 | Token::SubstringMatch
1069 | Token::CDO
1070 | Token::CDC
1071 | Token::ParenthesisBlock
1072 | Token::SquareBracketBlock
1073 | Token::CurlyBracketBlock
1074 | Token::CloseParenthesis
1075 | Token::CloseSquareBracket
1076 | Token::CloseCurlyBracket => {}
1077 }
1078 }
1079}
1080
1081fn integer_decode(v: f32) -> (u32, i16, i8) {
1084 let bits: u32 = unsafe { std::mem::transmute(v) };
1085 let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
1086 let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
1087 let mantissa = if exponent == 0 {
1088 (bits & 0x7fffff) << 1
1089 } else {
1090 (bits & 0x7fffff) | 0x800000
1091 };
1092 exponent -= 127 + 23;
1094 (mantissa, exponent, sign)
1095}
1096
1097impl<'i> TokenList<'i> {
1098 pub(crate) fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
1099 let mut fallbacks = ColorFallbackKind::empty();
1100 for token in &self.0 {
1101 match token {
1102 TokenOrValue::Color(color) => {
1103 fallbacks |= color.get_possible_fallbacks(targets);
1104 }
1105 TokenOrValue::Function(f) => {
1106 fallbacks |= f.arguments.get_necessary_fallbacks(targets);
1107 }
1108 TokenOrValue::Var(v) => {
1109 if let Some(fallback) = &v.fallback {
1110 fallbacks |= fallback.get_necessary_fallbacks(targets);
1111 }
1112 }
1113 TokenOrValue::Env(v) => {
1114 if let Some(fallback) = &v.fallback {
1115 fallbacks |= fallback.get_necessary_fallbacks(targets);
1116 }
1117 }
1118 _ => {}
1119 }
1120 }
1121
1122 fallbacks
1123 }
1124
1125 pub(crate) fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1126 let tokens = self
1127 .0
1128 .iter()
1129 .map(|token| match token {
1130 TokenOrValue::Color(color) => TokenOrValue::Color(color.get_fallback(kind)),
1131 TokenOrValue::Function(f) => TokenOrValue::Function(f.get_fallback(kind)),
1132 TokenOrValue::Var(v) => TokenOrValue::Var(v.get_fallback(kind)),
1133 TokenOrValue::Env(e) => TokenOrValue::Env(e.get_fallback(kind)),
1134 _ => token.clone(),
1135 })
1136 .collect();
1137 TokenList(tokens)
1138 }
1139
1140 pub(crate) fn get_fallbacks(&mut self, targets: Targets) -> Vec<(SupportsCondition<'i>, Self)> {
1141 let mut fallbacks = self.get_necessary_fallbacks(targets);
1144 let lowest_fallback = fallbacks.lowest();
1145 fallbacks.remove(lowest_fallback);
1146
1147 let mut res = Vec::new();
1148 if fallbacks.contains(ColorFallbackKind::P3) {
1149 res.push((
1150 ColorFallbackKind::P3.supports_condition(),
1151 self.get_fallback(ColorFallbackKind::P3),
1152 ));
1153 }
1154
1155 if fallbacks.contains(ColorFallbackKind::LAB) {
1156 res.push((
1157 ColorFallbackKind::LAB.supports_condition(),
1158 self.get_fallback(ColorFallbackKind::LAB),
1159 ));
1160 }
1161
1162 if !lowest_fallback.is_empty() {
1163 for token in self.0.iter_mut() {
1164 match token {
1165 TokenOrValue::Color(color) => {
1166 *color = color.get_fallback(lowest_fallback);
1167 }
1168 TokenOrValue::Function(f) => *f = f.get_fallback(lowest_fallback),
1169 TokenOrValue::Var(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),
1170 TokenOrValue::Env(v) if v.fallback.is_some() => *v = v.get_fallback(lowest_fallback),
1171 _ => {}
1172 }
1173 }
1174 }
1175
1176 res
1177 }
1178
1179 #[cfg(feature = "substitute_variables")]
1181 #[cfg_attr(docsrs, doc(cfg(feature = "substitute_variables")))]
1182 pub fn substitute_variables(&mut self, vars: &std::collections::HashMap<&str, TokenList<'i>>) {
1183 self.visit(&mut VarInliner { vars }).unwrap()
1184 }
1185}
1186
1187#[cfg(feature = "substitute_variables")]
1188struct VarInliner<'a, 'i> {
1189 vars: &'a std::collections::HashMap<&'a str, TokenList<'i>>,
1190}
1191
1192#[cfg(feature = "substitute_variables")]
1193impl<'a, 'i> crate::visitor::Visitor<'i> for VarInliner<'a, 'i> {
1194 type Error = std::convert::Infallible;
1195
1196 fn visit_types(&self) -> crate::visitor::VisitTypes {
1197 crate::visit_types!(TOKENS | VARIABLES)
1198 }
1199
1200 fn visit_token_list(&mut self, tokens: &mut TokenList<'i>) -> Result<(), Self::Error> {
1201 let mut i = 0;
1202 let mut seen = std::collections::HashSet::new();
1203 while i < tokens.0.len() {
1204 let token = &mut tokens.0[i];
1205 token.visit(self).unwrap();
1206 if let TokenOrValue::Var(var) = token {
1207 if let Some(value) = self.vars.get(var.name.ident.0.as_ref()) {
1208 if seen.insert(var.name.ident.0.clone()) {
1210 tokens.0.splice(i..i + 1, value.0.iter().cloned());
1211 continue;
1213 }
1214 } else if let Some(fallback) = &var.fallback {
1215 let fallback = fallback.0.clone();
1216 if seen.insert(var.name.ident.0.clone()) {
1217 tokens.0.splice(i..i + 1, fallback.into_iter());
1218 continue;
1219 }
1220 }
1221 }
1222 seen.clear();
1223 i += 1;
1224 }
1225 Ok(())
1226 }
1227}
1228
1229#[derive(Debug, Clone, PartialEq)]
1231#[cfg_attr(feature = "visitor", derive(Visit))]
1232#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1233#[cfg_attr(feature = "visitor", visit(visit_variable, VARIABLES))]
1234#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1235#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1236pub struct Variable<'i> {
1237 #[cfg_attr(feature = "serde", serde(borrow))]
1239 pub name: DashedIdentReference<'i>,
1240 pub fallback: Option<TokenList<'i>>,
1242}
1243
1244impl<'i> Variable<'i> {
1245 fn parse<'t>(
1246 input: &mut Parser<'i, 't>,
1247 options: &ParserOptions<'_, 'i>,
1248 depth: usize,
1249 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1250 let name = DashedIdentReference::parse_with_options(input, options)?;
1251
1252 let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
1253 Some(TokenList::parse(input, options, depth)?)
1254 } else {
1255 None
1256 };
1257
1258 Ok(Variable { name, fallback })
1259 }
1260
1261 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1262 where
1263 W: std::fmt::Write,
1264 {
1265 dest.write_str("var(")?;
1266 self.name.to_css(dest)?;
1267 if let Some(fallback) = &self.fallback {
1268 dest.delim(',', false)?;
1269 fallback.to_css(dest, is_custom_property)?;
1270 }
1271 dest.write_char(')')
1272 }
1273
1274 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1275 Variable {
1276 name: self.name.clone(),
1277 fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
1278 }
1279 }
1280}
1281
1282#[derive(Debug, Clone, PartialEq)]
1284#[cfg_attr(
1285 feature = "visitor",
1286 derive(Visit),
1287 visit(visit_environment_variable, ENVIRONMENT_VARIABLES)
1288)]
1289#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1290#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1291#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1292pub struct EnvironmentVariable<'i> {
1293 #[cfg_attr(feature = "serde", serde(borrow))]
1295 pub name: EnvironmentVariableName<'i>,
1296 #[cfg_attr(feature = "serde", serde(default))]
1298 pub indices: Vec<CSSInteger>,
1299 pub fallback: Option<TokenList<'i>>,
1301}
1302
1303#[derive(Debug, Clone, PartialEq)]
1305#[cfg_attr(feature = "visitor", derive(Visit))]
1306#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1307#[cfg_attr(
1308 feature = "serde",
1309 derive(serde::Serialize, serde::Deserialize),
1310 serde(tag = "type", rename_all = "lowercase")
1311)]
1312#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1313pub enum EnvironmentVariableName<'i> {
1314 #[cfg_attr(
1316 feature = "serde",
1317 serde(with = "crate::serialization::ValueWrapper::<UAEnvironmentVariable>")
1318 )]
1319 UA(UAEnvironmentVariable),
1320 #[cfg_attr(feature = "serde", serde(borrow))]
1322 Custom(DashedIdentReference<'i>),
1323 #[cfg_attr(feature = "serde", serde(with = "crate::serialization::ValueWrapper::<CustomIdent>"))]
1325 Unknown(CustomIdent<'i>),
1326}
1327
1328enum_property! {
1329 pub enum UAEnvironmentVariable {
1331 SafeAreaInsetTop,
1333 SafeAreaInsetRight,
1335 SafeAreaInsetBottom,
1337 SafeAreaInsetLeft,
1339 ViewportSegmentWidth,
1341 ViewportSegmentHeight,
1343 ViewportSegmentTop,
1345 ViewportSegmentLeft,
1347 ViewportSegmentBottom,
1349 ViewportSegmentRight,
1351 }
1352}
1353
1354impl<'i> EnvironmentVariableName<'i> {
1355 pub fn name(&self) -> &str {
1357 match self {
1358 EnvironmentVariableName::UA(ua) => ua.as_str(),
1359 EnvironmentVariableName::Custom(c) => c.ident.as_ref(),
1360 EnvironmentVariableName::Unknown(u) => u.0.as_ref(),
1361 }
1362 }
1363}
1364
1365impl<'i> Parse<'i> for EnvironmentVariableName<'i> {
1366 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1367 if let Ok(ua) = input.try_parse(UAEnvironmentVariable::parse) {
1368 return Ok(EnvironmentVariableName::UA(ua));
1369 }
1370
1371 if let Ok(dashed) =
1372 input.try_parse(|input| DashedIdentReference::parse_with_options(input, &ParserOptions::default()))
1373 {
1374 return Ok(EnvironmentVariableName::Custom(dashed));
1375 }
1376
1377 let ident = CustomIdent::parse(input)?;
1378 return Ok(EnvironmentVariableName::Unknown(ident));
1379 }
1380}
1381
1382impl<'i> ToCss for EnvironmentVariableName<'i> {
1383 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1384 where
1385 W: std::fmt::Write,
1386 {
1387 match self {
1388 EnvironmentVariableName::UA(ua) => ua.to_css(dest),
1389 EnvironmentVariableName::Custom(custom) => custom.to_css(dest),
1390 EnvironmentVariableName::Unknown(unknown) => unknown.to_css(dest),
1391 }
1392 }
1393}
1394
1395impl<'i> EnvironmentVariable<'i> {
1396 pub(crate) fn parse<'t>(
1397 input: &mut Parser<'i, 't>,
1398 options: &ParserOptions<'_, 'i>,
1399 depth: usize,
1400 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1401 input.expect_function_matching("env")?;
1402 input.parse_nested_block(|input| Self::parse_nested(input, options, depth))
1403 }
1404
1405 pub(crate) fn parse_nested<'t>(
1406 input: &mut Parser<'i, 't>,
1407 options: &ParserOptions<'_, 'i>,
1408 depth: usize,
1409 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1410 let name = EnvironmentVariableName::parse(input)?;
1411 let mut indices = Vec::new();
1412 while let Ok(index) = input.try_parse(CSSInteger::parse) {
1413 indices.push(index);
1414 }
1415
1416 let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
1417 Some(TokenList::parse(input, options, depth + 1)?)
1418 } else {
1419 None
1420 };
1421
1422 Ok(EnvironmentVariable {
1423 name,
1424 indices,
1425 fallback,
1426 })
1427 }
1428
1429 pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1430 where
1431 W: std::fmt::Write,
1432 {
1433 dest.write_str("env(")?;
1434 self.name.to_css(dest)?;
1435
1436 for item in &self.indices {
1437 dest.write_char(' ')?;
1438 item.to_css(dest)?;
1439 }
1440
1441 if let Some(fallback) = &self.fallback {
1442 dest.delim(',', false)?;
1443 fallback.to_css(dest, is_custom_property)?;
1444 }
1445 dest.write_char(')')
1446 }
1447
1448 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1449 EnvironmentVariable {
1450 name: self.name.clone(),
1451 indices: self.indices.clone(),
1452 fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
1453 }
1454 }
1455}
1456
1457#[derive(Debug, Clone, PartialEq)]
1459#[cfg_attr(feature = "visitor", derive(Visit))]
1460#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1461#[cfg_attr(feature = "visitor", visit(visit_function, FUNCTIONS))]
1462#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1463#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1464pub struct Function<'i> {
1465 #[cfg_attr(feature = "serde", serde(borrow))]
1467 pub name: Ident<'i>,
1468 pub arguments: TokenList<'i>,
1470}
1471
1472impl<'i> Function<'i> {
1473 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1474 where
1475 W: std::fmt::Write,
1476 {
1477 self.name.to_css(dest)?;
1478 dest.write_char('(')?;
1479 self.arguments.to_css(dest, is_custom_property)?;
1480 dest.write_char(')')
1481 }
1482
1483 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1484 Function {
1485 name: self.name.clone(),
1486 arguments: self.arguments.get_fallback(kind),
1487 }
1488 }
1489}
1490
1491#[derive(Debug, Clone, PartialEq)]
1496#[cfg_attr(feature = "visitor", derive(Visit))]
1497#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1498#[cfg_attr(
1499 feature = "serde",
1500 derive(serde::Serialize, serde::Deserialize),
1501 serde(tag = "type", rename_all = "lowercase")
1502)]
1503#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1504pub enum UnresolvedColor<'i> {
1505 RGB {
1507 r: f32,
1509 g: f32,
1511 b: f32,
1513 #[cfg_attr(feature = "serde", serde(borrow))]
1515 alpha: TokenList<'i>,
1516 },
1517 HSL {
1519 h: f32,
1521 s: f32,
1523 l: f32,
1525 #[cfg_attr(feature = "serde", serde(borrow))]
1527 alpha: TokenList<'i>,
1528 },
1529 #[cfg_attr(feature = "serde", serde(rename = "light-dark"))]
1531 LightDark {
1532 light: TokenList<'i>,
1534 dark: TokenList<'i>,
1536 },
1537}
1538
1539impl<'i> LightDarkColor for UnresolvedColor<'i> {
1540 #[inline]
1541 fn light_dark(light: Self, dark: Self) -> Self {
1542 UnresolvedColor::LightDark {
1543 light: TokenList(vec![TokenOrValue::UnresolvedColor(light)]),
1544 dark: TokenList(vec![TokenOrValue::UnresolvedColor(dark)]),
1545 }
1546 }
1547}
1548
1549impl<'i> UnresolvedColor<'i> {
1550 fn parse<'t>(
1551 f: &CowArcStr<'i>,
1552 input: &mut Parser<'i, 't>,
1553 options: &ParserOptions<'_, 'i>,
1554 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1555 let mut parser = ComponentParser::new(false);
1556 match_ignore_ascii_case! { &*f,
1557 "rgb" => {
1558 input.parse_nested_block(|input| {
1559 parser.parse_relative::<SRGB, _, _>(input, |input, parser| {
1560 let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
1561 if is_legacy {
1562 return Err(input.new_custom_error(ParserError::InvalidValue))
1563 }
1564 input.expect_delim('/')?;
1565 let alpha = TokenList::parse(input, options, 0)?;
1566 Ok(UnresolvedColor::RGB { r, g, b, alpha })
1567 })
1568 })
1569 },
1570 "hsl" => {
1571 input.parse_nested_block(|input| {
1572 parser.parse_relative::<HSL, _, _>(input, |input, parser| {
1573 let (h, s, l, is_legacy) = parse_hsl_hwb_components::<HSL>(input, parser, false)?;
1574 if is_legacy {
1575 return Err(input.new_custom_error(ParserError::InvalidValue))
1576 }
1577 input.expect_delim('/')?;
1578 let alpha = TokenList::parse(input, options, 0)?;
1579 Ok(UnresolvedColor::HSL { h, s, l, alpha })
1580 })
1581 })
1582 },
1583 "light-dark" => {
1584 input.parse_nested_block(|input| {
1585 let light = input.parse_until_before(Delimiter::Comma, |input|
1586 TokenList::parse(input, options, 0)
1587 )?;
1588 input.expect_comma()?;
1589 let dark = TokenList::parse(input, options, 0)?;
1590 Ok(UnresolvedColor::LightDark { light, dark })
1591 })
1592 },
1593 _ => Err(input.new_custom_error(ParserError::InvalidValue))
1594 }
1595 }
1596
1597 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1598 where
1599 W: std::fmt::Write,
1600 {
1601 #[inline]
1602 fn c(c: &f32) -> i32 {
1603 (c * 255.0).round().clamp(0.0, 255.0) as i32
1604 }
1605
1606 match self {
1607 UnresolvedColor::RGB { r, g, b, alpha } => {
1608 if should_compile!(dest.targets, SpaceSeparatedColorNotation) {
1609 dest.write_str("rgba(")?;
1610 c(r).to_css(dest)?;
1611 dest.delim(',', false)?;
1612 c(g).to_css(dest)?;
1613 dest.delim(',', false)?;
1614 c(b).to_css(dest)?;
1615 dest.delim(',', false)?;
1616 alpha.to_css(dest, is_custom_property)?;
1617 dest.write_char(')')?;
1618 return Ok(());
1619 }
1620
1621 dest.write_str("rgb(")?;
1622 c(r).to_css(dest)?;
1623 dest.write_char(' ')?;
1624 c(g).to_css(dest)?;
1625 dest.write_char(' ')?;
1626 c(b).to_css(dest)?;
1627 dest.delim('/', true)?;
1628 alpha.to_css(dest, is_custom_property)?;
1629 dest.write_char(')')
1630 }
1631 UnresolvedColor::HSL { h, s, l, alpha } => {
1632 if should_compile!(dest.targets, SpaceSeparatedColorNotation) {
1633 dest.write_str("hsla(")?;
1634 h.to_css(dest)?;
1635 dest.delim(',', false)?;
1636 Percentage(*s).to_css(dest)?;
1637 dest.delim(',', false)?;
1638 Percentage(*l).to_css(dest)?;
1639 dest.delim(',', false)?;
1640 alpha.to_css(dest, is_custom_property)?;
1641 dest.write_char(')')?;
1642 return Ok(());
1643 }
1644
1645 dest.write_str("hsl(")?;
1646 h.to_css(dest)?;
1647 dest.write_char(' ')?;
1648 Percentage(*s).to_css(dest)?;
1649 dest.write_char(' ')?;
1650 Percentage(*l).to_css(dest)?;
1651 dest.delim('/', true)?;
1652 alpha.to_css(dest, is_custom_property)?;
1653 dest.write_char(')')
1654 }
1655 UnresolvedColor::LightDark { light, dark } => {
1656 if !dest.targets.is_compatible(crate::compat::Feature::LightDark) {
1657 dest.write_str("var(--lightningcss-light")?;
1658 dest.delim(',', false)?;
1659 light.to_css(dest, is_custom_property)?;
1660 dest.write_char(')')?;
1661 dest.whitespace()?;
1662 dest.write_str("var(--lightningcss-dark")?;
1663 dest.delim(',', false)?;
1664 dark.to_css(dest, is_custom_property)?;
1665 return dest.write_char(')');
1666 }
1667
1668 dest.write_str("light-dark(")?;
1669 light.to_css(dest, is_custom_property)?;
1670 dest.delim(',', false)?;
1671 dark.to_css(dest, is_custom_property)?;
1672 dest.write_char(')')
1673 }
1674 }
1675 }
1676}