component.rs (7559B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Parse/serialize and resolve a single color component. 6 7 use std::fmt::Write; 8 9 use super::{ 10 parsing::{rcs_enabled, ChannelKeyword}, 11 AbsoluteColor, 12 }; 13 use crate::derives::*; 14 use crate::{ 15 parser::ParserContext, 16 values::{ 17 animated::ToAnimatedValue, 18 generics::calc::{CalcUnits, GenericCalcNode}, 19 specified::calc::{AllowParse, Leaf}, 20 }, 21 }; 22 use cssparser::{color::OPAQUE, Parser, Token}; 23 use style_traits::{ParseError, ToCss}; 24 25 /// A single color component. 26 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] 27 #[repr(u8)] 28 pub enum ColorComponent<ValueType> { 29 /// The "none" keyword. 30 None, 31 /// A absolute value. 32 Value(ValueType), 33 /// A channel keyword, e.g. `r`, `l`, `alpha`, etc. 34 ChannelKeyword(ChannelKeyword), 35 /// A calc() value. 36 Calc(Box<GenericCalcNode<Leaf>>), 37 /// Used when alpha components are not specified. 38 AlphaOmitted, 39 } 40 41 impl<ValueType> ColorComponent<ValueType> { 42 /// Return true if the component is "none". 43 #[inline] 44 pub fn is_none(&self) -> bool { 45 matches!(self, Self::None) 46 } 47 } 48 49 /// An utility trait that allows the construction of [ColorComponent] 50 /// `ValueType`'s after parsing a color component. 51 pub trait ColorComponentType: Sized + Clone { 52 // TODO(tlouw): This function should be named according to the rules in the spec 53 // stating that all the values coming from color components are 54 // numbers and that each has their own rules dependeing on types. 55 /// Construct a new component from a single value. 56 fn from_value(value: f32) -> Self; 57 58 /// Return the [CalcUnits] flags that the impl can handle. 59 fn units() -> CalcUnits; 60 61 /// Try to create a new component from the given token. 62 fn try_from_token(token: &Token) -> Result<Self, ()>; 63 64 /// Try to create a new component from the given [CalcNodeLeaf] that was 65 /// resolved from a [CalcNode]. 66 fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()>; 67 } 68 69 impl<ValueType: ColorComponentType> ColorComponent<ValueType> { 70 /// Parse a single [ColorComponent]. 71 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 // TODO(tlouw): We only have to simplify the node when we have to store it, but we 98 // only know if we have to store it much later when the whole color 99 // can't be resolved to absolute at which point the calc nodes are 100 // burried deep in a [ColorFunction] struct. 101 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 /// Resolve a [ColorComponent] into a float. None is "none". 112 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 // <https://drafts.csswg.org/css-color-5/#rcs-intro> 145 // If the alpha value of the relative color is omitted, it defaults to that of 146 // the origin color (rather than defaulting to 100%, as it does in the absolute 147 // syntax). 148 origin_color.alpha().map(ValueType::from_value) 149 } else { 150 Some(ValueType::from_value(OPAQUE)) 151 } 152 }, 153 }) 154 } 155 } 156 157 impl<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 // When we only have a channel keyword in a leaf node, we should serialize it with 168 // calc(..), except when one of the rgb color space functions are used, e.g. 169 // rgb(..), hsl(..) or hwb(..) for historical reasons. 170 // <https://github.com/web-platform-tests/wpt/issues/47921> 171 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 182 impl<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 }