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" | "alpha" => {
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 = f32::to_bits(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.write_char(',')?;
1219 if !fallback.starts_with_whitespace() {
1220 dest.whitespace()?;
1221 }
1222 fallback.to_css(dest, is_custom_property)?;
1223 }
1224 dest.write_char(')')
1225 }
1226
1227 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1228 Variable {
1229 name: self.name.clone(),
1230 fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
1231 }
1232 }
1233}
1234
1235#[derive(Debug, Clone, PartialEq)]
1237#[cfg_attr(
1238 feature = "visitor",
1239 derive(Visit),
1240 visit(visit_environment_variable, ENVIRONMENT_VARIABLES)
1241)]
1242#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1243#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1244#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1245pub struct EnvironmentVariable<'i> {
1246 #[cfg_attr(feature = "serde", serde(borrow))]
1248 pub name: EnvironmentVariableName<'i>,
1249 #[cfg_attr(feature = "serde", serde(default))]
1251 pub indices: Vec<CSSInteger>,
1252 pub fallback: Option<TokenList<'i>>,
1254}
1255
1256#[derive(Debug, Clone, PartialEq)]
1258#[cfg_attr(feature = "visitor", derive(Visit))]
1259#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1260#[cfg_attr(
1261 feature = "serde",
1262 derive(serde::Serialize, serde::Deserialize),
1263 serde(tag = "type", rename_all = "lowercase")
1264)]
1265#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1266pub enum EnvironmentVariableName<'i> {
1267 #[cfg_attr(
1269 feature = "serde",
1270 serde(with = "crate::serialization::ValueWrapper::<UAEnvironmentVariable>")
1271 )]
1272 UA(UAEnvironmentVariable),
1273 #[cfg_attr(feature = "serde", serde(borrow))]
1275 Custom(DashedIdentReference<'i>),
1276 #[cfg_attr(feature = "serde", serde(with = "crate::serialization::ValueWrapper::<CustomIdent>"))]
1278 Unknown(CustomIdent<'i>),
1279}
1280
1281enum_property! {
1282 pub enum UAEnvironmentVariable {
1284 SafeAreaInsetTop,
1286 SafeAreaInsetRight,
1288 SafeAreaInsetBottom,
1290 SafeAreaInsetLeft,
1292 ViewportSegmentWidth,
1294 ViewportSegmentHeight,
1296 ViewportSegmentTop,
1298 ViewportSegmentLeft,
1300 ViewportSegmentBottom,
1302 ViewportSegmentRight,
1304 }
1305}
1306
1307impl<'i> EnvironmentVariableName<'i> {
1308 pub fn name(&self) -> &str {
1310 match self {
1311 EnvironmentVariableName::UA(ua) => ua.as_str(),
1312 EnvironmentVariableName::Custom(c) => c.ident.as_ref(),
1313 EnvironmentVariableName::Unknown(u) => u.0.as_ref(),
1314 }
1315 }
1316}
1317
1318impl<'i> Parse<'i> for EnvironmentVariableName<'i> {
1319 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1320 if let Ok(ua) = input.try_parse(UAEnvironmentVariable::parse) {
1321 return Ok(EnvironmentVariableName::UA(ua));
1322 }
1323
1324 if let Ok(dashed) =
1325 input.try_parse(|input| DashedIdentReference::parse_with_options(input, &ParserOptions::default()))
1326 {
1327 return Ok(EnvironmentVariableName::Custom(dashed));
1328 }
1329
1330 let ident = CustomIdent::parse(input)?;
1331 return Ok(EnvironmentVariableName::Unknown(ident));
1332 }
1333}
1334
1335impl<'i> ToCss for EnvironmentVariableName<'i> {
1336 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1337 where
1338 W: std::fmt::Write,
1339 {
1340 match self {
1341 EnvironmentVariableName::UA(ua) => ua.to_css(dest),
1342 EnvironmentVariableName::Custom(custom) => custom.to_css(dest),
1343 EnvironmentVariableName::Unknown(unknown) => unknown.to_css(dest),
1344 }
1345 }
1346}
1347
1348impl<'i> EnvironmentVariable<'i> {
1349 pub(crate) fn parse<'t>(
1350 input: &mut Parser<'i, 't>,
1351 options: &ParserOptions<'i>,
1352 depth: usize,
1353 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1354 input.expect_function_matching("env")?;
1355 input.parse_nested_block(|input| Self::parse_nested(input, options, depth))
1356 }
1357
1358 pub(crate) fn parse_nested<'t>(
1359 input: &mut Parser<'i, 't>,
1360 options: &ParserOptions<'i>,
1361 depth: usize,
1362 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1363 let name = EnvironmentVariableName::parse(input)?;
1364 let mut indices = Vec::new();
1365 while let Ok(index) = input.try_parse(CSSInteger::parse) {
1366 indices.push(index);
1367 }
1368
1369 let fallback = if input.try_parse(|input| input.expect_comma()).is_ok() {
1370 Some(TokenList::parse(input, options, depth + 1)?)
1371 } else {
1372 None
1373 };
1374
1375 Ok(EnvironmentVariable {
1376 name,
1377 indices,
1378 fallback,
1379 })
1380 }
1381
1382 pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1383 where
1384 W: std::fmt::Write,
1385 {
1386 dest.write_str("env(")?;
1387 self.name.to_css(dest)?;
1388
1389 for item in &self.indices {
1390 dest.write_char(' ')?;
1391 item.to_css(dest)?;
1392 }
1393
1394 if let Some(fallback) = &self.fallback {
1395 dest.write_char(',')?;
1396 if !fallback.starts_with_whitespace() {
1397 dest.whitespace()?;
1398 }
1399 fallback.to_css(dest, is_custom_property)?;
1400 }
1401 dest.write_char(')')
1402 }
1403
1404 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1405 EnvironmentVariable {
1406 name: self.name.clone(),
1407 indices: self.indices.clone(),
1408 fallback: self.fallback.as_ref().map(|fallback| fallback.get_fallback(kind)),
1409 }
1410 }
1411}
1412
1413#[derive(Debug, Clone, PartialEq)]
1415#[cfg_attr(feature = "visitor", derive(Visit))]
1416#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1417#[cfg_attr(feature = "visitor", visit(visit_function, FUNCTIONS))]
1418#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1419#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1420pub struct Function<'i> {
1421 #[cfg_attr(feature = "serde", serde(borrow))]
1423 pub name: Ident<'i>,
1424 pub arguments: TokenList<'i>,
1426}
1427
1428impl<'i> Function<'i> {
1429 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1430 where
1431 W: std::fmt::Write,
1432 {
1433 self.name.to_css(dest)?;
1434 dest.write_char('(')?;
1435 self.arguments.to_css(dest, is_custom_property)?;
1436 dest.write_char(')')
1437 }
1438
1439 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
1440 Function {
1441 name: self.name.clone(),
1442 arguments: self.arguments.get_fallback(kind),
1443 }
1444 }
1445}
1446
1447#[derive(Debug, Clone, PartialEq)]
1452#[cfg_attr(feature = "visitor", derive(Visit))]
1453#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1454#[cfg_attr(
1455 feature = "serde",
1456 derive(serde::Serialize, serde::Deserialize),
1457 serde(tag = "type", rename_all = "lowercase")
1458)]
1459#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1460pub enum UnresolvedColor<'i> {
1461 RGB {
1463 r: f32,
1465 g: f32,
1467 b: f32,
1469 #[cfg_attr(feature = "serde", serde(borrow))]
1471 alpha: TokenList<'i>,
1472 },
1473 HSL {
1475 h: f32,
1477 s: f32,
1479 l: f32,
1481 #[cfg_attr(feature = "serde", serde(borrow))]
1483 alpha: TokenList<'i>,
1484 },
1485 #[cfg_attr(feature = "serde", serde(rename = "light-dark"))]
1487 LightDark {
1488 light: TokenList<'i>,
1490 dark: TokenList<'i>,
1492 },
1493}
1494
1495impl<'i> LightDarkColor for UnresolvedColor<'i> {
1496 #[inline]
1497 fn light_dark(light: Self, dark: Self) -> Self {
1498 UnresolvedColor::LightDark {
1499 light: TokenList(vec![TokenOrValue::UnresolvedColor(light)]),
1500 dark: TokenList(vec![TokenOrValue::UnresolvedColor(dark)]),
1501 }
1502 }
1503}
1504
1505impl<'i> UnresolvedColor<'i> {
1506 fn parse<'t>(
1507 f: &CowArcStr<'i>,
1508 input: &mut Parser<'i, 't>,
1509 options: &ParserOptions<'i>,
1510 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1511 let mut parser = ComponentParser::new(false);
1512 match_ignore_ascii_case! { &*f,
1513 "rgb" => {
1514 input.parse_nested_block(|input| {
1515 parser.parse_relative::<RGB, _, _>(input, |input, parser| {
1516 let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
1517 if is_legacy {
1518 return Err(input.new_custom_error(ParserError::InvalidValue))
1519 }
1520 input.expect_delim('/')?;
1521 let alpha = TokenList::parse(input, options, 0)?;
1522 Ok(UnresolvedColor::RGB { r, g, b, alpha })
1523 })
1524 })
1525 },
1526 "hsl" => {
1527 input.parse_nested_block(|input| {
1528 parser.parse_relative::<HSL, _, _>(input, |input, parser| {
1529 let (h, s, l, is_legacy) = parse_hsl_hwb_components::<HSL>(input, parser, false)?;
1530 if is_legacy {
1531 return Err(input.new_custom_error(ParserError::InvalidValue))
1532 }
1533 input.expect_delim('/')?;
1534 let alpha = TokenList::parse(input, options, 0)?;
1535 Ok(UnresolvedColor::HSL { h, s, l, alpha })
1536 })
1537 })
1538 },
1539 "light-dark" => {
1540 input.parse_nested_block(|input| {
1541 let light = input.parse_until_before(Delimiter::Comma, |input|
1542 TokenList::parse(input, options, 0)
1543 )?;
1544 input.expect_comma()?;
1545 let dark = TokenList::parse(input, options, 0)?;
1546 Ok(UnresolvedColor::LightDark { light, dark })
1547 })
1548 },
1549 _ => Err(input.new_custom_error(ParserError::InvalidValue))
1550 }
1551 }
1552
1553 fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError>
1554 where
1555 W: std::fmt::Write,
1556 {
1557 match self {
1558 UnresolvedColor::RGB { r, g, b, alpha } => {
1559 if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {
1560 dest.write_str("rgba(")?;
1561 r.to_css(dest)?;
1562 dest.delim(',', false)?;
1563 g.to_css(dest)?;
1564 dest.delim(',', false)?;
1565 b.to_css(dest)?;
1566 dest.delim(',', false)?;
1567 alpha.to_css(dest, is_custom_property)?;
1568 dest.write_char(')')?;
1569 return Ok(());
1570 }
1571
1572 dest.write_str("rgb(")?;
1573 r.to_css(dest)?;
1574 dest.write_char(' ')?;
1575 g.to_css(dest)?;
1576 dest.write_char(' ')?;
1577 b.to_css(dest)?;
1578 dest.delim('/', true)?;
1579 alpha.to_css(dest, is_custom_property)?;
1580 dest.write_char(')')
1581 }
1582 UnresolvedColor::HSL { h, s, l, alpha } => {
1583 if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) {
1584 dest.write_str("hsla(")?;
1585 h.to_css(dest)?;
1586 dest.delim(',', false)?;
1587 Percentage(*s / 100.0).to_css(dest)?;
1588 dest.delim(',', false)?;
1589 Percentage(*l / 100.0).to_css(dest)?;
1590 dest.delim(',', false)?;
1591 alpha.to_css(dest, is_custom_property)?;
1592 dest.write_char(')')?;
1593 return Ok(());
1594 }
1595
1596 dest.write_str("hsl(")?;
1597 h.to_css(dest)?;
1598 dest.write_char(' ')?;
1599 Percentage(*s / 100.0).to_css(dest)?;
1600 dest.write_char(' ')?;
1601 Percentage(*l / 100.0).to_css(dest)?;
1602 dest.delim('/', true)?;
1603 alpha.to_css(dest, is_custom_property)?;
1604 dest.write_char(')')
1605 }
1606 UnresolvedColor::LightDark { light, dark } => {
1607 if should_compile!(dest.targets.current, LightDark) {
1608 dest.write_str("var(--lightningcss-light")?;
1609 dest.delim(',', false)?;
1610 light.to_css(dest, is_custom_property)?;
1611 dest.write_char(')')?;
1612 dest.whitespace()?;
1613 dest.write_str("var(--lightningcss-dark")?;
1614 dest.delim(',', false)?;
1615 dark.to_css(dest, is_custom_property)?;
1616 return dest.write_char(')');
1617 }
1618
1619 dest.write_str("light-dark(")?;
1620 light.to_css(dest, is_custom_property)?;
1621 dest.delim(',', false)?;
1622 dark.to_css(dest, is_custom_property)?;
1623 dest.write_char(')')
1624 }
1625 }
1626 }
1627}