1use std::fmt::Write;
8
9use super::{
10 parsing::{rcs_enabled, ChannelKeyword},
11 AbsoluteColor,
12};
13use crate::derives::*;
14use crate::{
15 parser::ParserContext,
16 values::{
17 animated::ToAnimatedValue,
18 generics::calc::{CalcUnits, GenericCalcNode},
19 specified::calc::{AllowParse, Leaf},
20 },
21};
22use cssparser::{color::OPAQUE, Parser, Token};
23use style_traits::{ParseError, ToCss};
24
25#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
27#[repr(u8)]
28pub enum ColorComponent<ValueType> {
29 None,
31 Value(ValueType),
33 ChannelKeyword(ChannelKeyword),
35 Calc(Box<GenericCalcNode<Leaf>>),
37 AlphaOmitted,
39}
40
41impl<ValueType> ColorComponent<ValueType> {
42 #[inline]
44 pub fn is_none(&self) -> bool {
45 matches!(self, Self::None)
46 }
47}
48
49pub trait ColorComponentType: Sized + Clone {
52 fn from_value(value: f32) -> Self;
57
58 fn units() -> CalcUnits;
60
61 fn try_from_token(token: &Token) -> Result<Self, ()>;
63
64 fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()>;
67}
68
69impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
70 pub fn parse<'i, 't>(
72 context: &ParserContext,
73 input: &mut Parser<'i, 't>,
74 allow_none: bool,
75 ) -> Result<Self, ParseError<'i>> {
76 let location = input.current_source_location();
77
78 match *input.next()? {
79 Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
80 Ok(ColorComponent::None)
81 },
82 ref t @ Token::Ident(ref ident) => {
83 let Ok(channel_keyword) = ChannelKeyword::from_ident(ident) else {
84 return Err(location.new_unexpected_token_error(t.clone()));
85 };
86 Ok(ColorComponent::ChannelKeyword(channel_keyword))
87 },
88 Token::Function(ref name) => {
89 let function = GenericCalcNode::math_function(context, name, location)?;
90 let allow = AllowParse::new(if rcs_enabled() {
91 ValueType::units() | CalcUnits::COLOR_COMPONENT
92 } else {
93 ValueType::units()
94 });
95 let mut node = GenericCalcNode::parse(context, input, function, allow)?;
96
97 node.simplify_and_sort();
102
103 Ok(Self::Calc(Box::new(node)))
104 },
105 ref t => ValueType::try_from_token(t)
106 .map(Self::Value)
107 .map_err(|_| location.new_unexpected_token_error(t.clone())),
108 }
109 }
110
111 pub fn resolve(&self, origin_color: Option<&AbsoluteColor>) -> Result<Option<ValueType>, ()> {
113 Ok(match self {
114 ColorComponent::None => None,
115 ColorComponent::Value(value) => Some(value.clone()),
116 ColorComponent::ChannelKeyword(channel_keyword) => match origin_color {
117 Some(origin_color) => {
118 let value = origin_color.get_component_by_channel_keyword(*channel_keyword)?;
119 Some(ValueType::from_value(value.unwrap_or(0.0)))
120 },
121 None => return Err(()),
122 },
123 ColorComponent::Calc(node) => {
124 let Ok(resolved_leaf) = node.resolve_map(|leaf| {
125 Ok(match leaf {
126 Leaf::ColorComponent(channel_keyword) => match origin_color {
127 Some(origin_color) => {
128 let value = origin_color
129 .get_component_by_channel_keyword(*channel_keyword)?;
130 Leaf::Number(value.unwrap_or(0.0))
131 },
132 None => return Err(()),
133 },
134 l => l.clone(),
135 })
136 }) else {
137 return Err(());
138 };
139
140 Some(ValueType::try_from_leaf(&resolved_leaf)?)
141 },
142 ColorComponent::AlphaOmitted => {
143 if let Some(origin_color) = origin_color {
144 origin_color.alpha().map(ValueType::from_value)
149 } else {
150 Some(ValueType::from_value(OPAQUE))
151 }
152 },
153 })
154 }
155}
156
157impl<ValueType: ToCss> ToCss for ColorComponent<ValueType> {
158 fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
159 where
160 W: Write,
161 {
162 match self {
163 ColorComponent::None => dest.write_str("none")?,
164 ColorComponent::Value(value) => value.to_css(dest)?,
165 ColorComponent::ChannelKeyword(channel_keyword) => channel_keyword.to_css(dest)?,
166 ColorComponent::Calc(node) => {
167 node.to_css(dest)?;
172 },
173 ColorComponent::AlphaOmitted => {
174 debug_assert!(false, "can't serialize an omitted alpha component");
175 },
176 }
177
178 Ok(())
179 }
180}
181
182impl<ValueType> ToAnimatedValue for ColorComponent<ValueType> {
183 type AnimatedValue = Self;
184
185 fn to_animated_value(self, _context: &crate::values::animated::Context) -> Self::AnimatedValue {
186 self
187 }
188
189 fn from_animated_value(animated: Self::AnimatedValue) -> Self {
190 animated
191 }
192}