1use std::fmt::Write;
8
9use super::{AbsoluteColor, parsing::ChannelKeyword};
10use crate::derives::*;
11use crate::typed_om::NumericType;
12use crate::{
13 parser::ParserContext,
14 values::{
15 animated::ToAnimatedValue,
16 computed,
17 specified::calc::{CalcParseFlags, Leaf, PercentageContext, SpecifiedCalcNode},
18 },
19};
20use cssparser::{Parser, Token, color::OPAQUE};
21use style_traits::{ParseError, StyleParseErrorKind, ToCss};
22
23#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
25#[repr(u8)]
26pub enum ColorComponent<ValueType> {
27 None,
29 Value(ValueType),
31 ChannelKeyword(ChannelKeyword),
33 Calc(Box<SpecifiedCalcNode>),
35 AlphaOmitted,
37}
38
39impl<ValueType> ColorComponent<ValueType> {
40 #[inline]
42 pub fn is_none(&self) -> bool {
43 matches!(self, Self::None)
44 }
45}
46
47pub trait ColorComponentType: Sized + Clone {
50 fn from_value(value: f32) -> Self;
55
56 fn is_valid_type(ty: &NumericType) -> bool;
58
59 fn try_from_token(token: &Token) -> Result<Self, ()>;
61
62 fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()>;
65}
66
67impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
68 pub fn parse(
70 context: &ParserContext,
71 input: &mut Parser,
72 allow_none: bool,
73 allowed_channel_keywords: ChannelKeyword,
74 percentage_context: PercentageContext,
75 ) -> Result<Self, ParseError> {
76 match *input.next()? {
77 Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
78 Ok(ColorComponent::None)
79 },
80 Token::Ident(ref ident) => Ok(match ChannelKeyword::from_ident(ident) {
81 Ok(channel_keyword) if allowed_channel_keywords.contains(channel_keyword) => {
82 ColorComponent::ChannelKeyword(channel_keyword)
83 },
84 _ => return Err(ParseError::unexpected_token()),
85 }),
86 Token::Function(ref name) => {
87 let function = SpecifiedCalcNode::math_function(context, name)?;
88 let mut flags = CalcParseFlags::new(percentage_context);
89 flags.color_components = allowed_channel_keywords;
90 let mut node = SpecifiedCalcNode::parse(context, input, function, flags)?;
91 node.simplify_and_sort();
92 if !node
93 .numeric_type()
94 .is_ok_and(|ty| ValueType::is_valid_type(&ty))
95 {
96 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
97 }
98 Ok(Self::Calc(Box::new(node)))
99 },
100 ref t => ValueType::try_from_token(t)
101 .map(Self::Value)
102 .map_err(|_| ParseError::unexpected_token()),
103 }
104 }
105
106 pub fn to_computed_value(
113 &self,
114 context: Option<&computed::Context>,
115 origin_color: Option<&AbsoluteColor>,
116 ) -> Result<Self, ()> {
117 Ok(match self {
118 Self::None => Self::None,
119 Self::Value(v) => Self::Value(v.clone()),
120 Self::ChannelKeyword(channel_keyword) => {
121 let channel_value = origin_color
122 .and_then(|c| c.get_component_by_channel_keyword(*channel_keyword).ok());
123 match channel_value {
124 Some(value) => Self::Value(ValueType::from_value(value.unwrap_or(0.0))),
125 None => Self::ChannelKeyword(*channel_keyword),
128 }
129 },
130 Self::Calc(node) => {
131 let mut computed = node.to_computed_value(context, origin_color)?;
135 computed.simplify_and_sort();
136 if let Some(leaf) = computed.as_leaf()
137 && let Ok(value) = ValueType::try_from_leaf(&leaf)
138 {
139 Self::Value(value)
140 } else {
141 Self::Calc(Box::new(computed))
142 }
143 },
144 Self::AlphaOmitted => match origin_color {
145 Some(origin_color) => match origin_color.alpha() {
150 Some(alpha) => Self::Value(ValueType::from_value(alpha)),
151 None => Self::None,
152 },
153 None => Self::AlphaOmitted,
154 },
155 })
156 }
157
158 pub fn resolve(&self) -> Result<Option<ValueType>, ()> {
163 Ok(match self {
164 Self::None => None,
165 Self::Value(value) => Some(value.clone()),
166 Self::ChannelKeyword(_) => return Err(()),
169 Self::Calc(node) => Some(ValueType::try_from_leaf(&node.resolve()?)?),
170 Self::AlphaOmitted => Some(ValueType::from_value(OPAQUE)),
171 })
172 }
173}
174
175impl<ValueType: ToCss> ToCss for ColorComponent<ValueType> {
176 fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
177 where
178 W: Write,
179 {
180 match self {
181 ColorComponent::None => dest.write_str("none")?,
182 ColorComponent::Value(value) => value.to_css(dest)?,
183 ColorComponent::ChannelKeyword(channel_keyword) => channel_keyword.to_css(dest)?,
184 ColorComponent::Calc(node) => {
185 node.to_css(dest)?;
190 },
191 ColorComponent::AlphaOmitted => {
192 debug_assert!(false, "can't serialize an omitted alpha component");
193 },
194 }
195
196 Ok(())
197 }
198}
199
200impl<ValueType> ToAnimatedValue for ColorComponent<ValueType> {
201 type AnimatedValue = Self;
202
203 fn to_animated_value(self, _context: &crate::values::animated::Context) -> Self::AnimatedValue {
204 self
205 }
206
207 fn from_animated_value(animated: Self::AnimatedValue) -> Self {
208 animated
209 }
210}