1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Parse/serialize and resolve a single color component.
use std::fmt::Write;
use super::{
parsing::{rcs_enabled, ChannelKeyword},
AbsoluteColor,
};
use crate::{
parser::ParserContext,
values::{
animated::ToAnimatedValue,
generics::calc::{CalcUnits, GenericCalcNode},
specified::calc::{AllowParse, Leaf},
},
};
use cssparser::{color::OPAQUE, Parser, Token};
use style_traits::{ParseError, ToCss};
/// A single color component.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[repr(u8)]
pub enum ColorComponent<ValueType> {
/// The "none" keyword.
None,
/// A absolute value.
Value(ValueType),
/// A calc() value.
Calc(Box<GenericCalcNode<Leaf>>),
/// Used when alpha components are not specified.
AlphaOmitted,
}
impl<ValueType> ColorComponent<ValueType> {
/// Return true if the component is "none".
#[inline]
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
/// An utility trait that allows the construction of [ColorComponent]
/// `ValueType`'s after parsing a color component.
pub trait ColorComponentType: Sized + Clone {
// TODO(tlouw): This function should be named according to the rules in the spec
// stating that all the values coming from color components are
// numbers and that each has their own rules dependeing on types.
/// Construct a new component from a single value.
fn from_value(value: f32) -> Self;
/// Return the [CalcUnits] flags that the impl can handle.
fn units() -> CalcUnits;
/// Try to create a new component from the given token.
fn try_from_token(token: &Token) -> Result<Self, ()>;
/// Try to create a new component from the given [CalcNodeLeaf] that was
/// resolved from a [CalcNode].
fn try_from_leaf(leaf: &Leaf) -> Result<Self, ()>;
}
impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
/// Parse a single [ColorComponent].
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
Ok(ColorComponent::None)
},
ref t @ Token::Ident(ref ident) => {
let Ok(channel_keyword) = ChannelKeyword::from_ident(ident) else {
return Err(location.new_unexpected_token_error(t.clone()));
};
let node = GenericCalcNode::Leaf(Leaf::ColorComponent(channel_keyword));
Ok(ColorComponent::Calc(Box::new(node)))
},
Token::Function(ref name) => {
let function = GenericCalcNode::math_function(context, name, location)?;
let allow = AllowParse::new(if rcs_enabled() {
ValueType::units() | CalcUnits::COLOR_COMPONENT
} else {
ValueType::units()
});
let mut node = GenericCalcNode::parse(context, input, function, allow)?;
// TODO(tlouw): We only have to simplify the node when we have to store it, but we
// only know if we have to store it much later when the whole color
// can't be resolved to absolute at which point the calc nodes are
// burried deep in a [ColorFunction] struct.
node.simplify_and_sort();
Ok(Self::Calc(Box::new(node)))
},
ref t => ValueType::try_from_token(t)
.map(Self::Value)
.map_err(|_| location.new_unexpected_token_error(t.clone())),
}
}
/// Resolve a [ColorComponent] into a float. None is "none".
pub fn resolve(&self, origin_color: Option<&AbsoluteColor>) -> Result<Option<ValueType>, ()> {
Ok(match self {
ColorComponent::None => None,
ColorComponent::Value(value) => Some(value.clone()),
ColorComponent::Calc(node) => {
let Ok(resolved_leaf) = node.resolve_map(|leaf| {
Ok(match leaf {
Leaf::ColorComponent(channel_keyword) => {
if let Some(origin_color) = origin_color {
if let Ok(value) =
origin_color.get_component_by_channel_keyword(*channel_keyword)
{
Leaf::Number(value.unwrap_or(0.0))
} else {
return Err(());
}
} else {
return Err(());
}
},
l => l.clone(),
})
}, |_| Err(())) else {
return Err(());
};
Some(ValueType::try_from_leaf(&resolved_leaf)?)
},
ColorComponent::AlphaOmitted => {
if let Some(origin_color) = origin_color {
// <https://drafts.csswg.org/css-color-5/#rcs-intro>
// If the alpha value of the relative color is omitted, it defaults to that of
// the origin color (rather than defaulting to 100%, as it does in the absolute
// syntax).
origin_color.alpha().map(ValueType::from_value)
} else {
Some(ValueType::from_value(OPAQUE))
}
},
})
}
}
impl<ValueType: ToCss> ToCss for ColorComponent<ValueType> {
fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
where
W: Write,
{
match self {
ColorComponent::None => dest.write_str("none")?,
ColorComponent::Value(value) => value.to_css(dest)?,
ColorComponent::Calc(node) => {
// Channel keywords used directly as a component serializes without `calc()`, but
// we store channel keywords inside a calc node irrespectively, so we have to remove
// it again here.
// There are some contradicting wpt's, which depends on resolution of:
// <https://github.com/web-platform-tests/wpt/issues/47921>
if let GenericCalcNode::Leaf(Leaf::ColorComponent(channel_keyword)) = node.as_ref()
{
channel_keyword.to_css(dest)?;
} else {
node.to_css(dest)?;
}
},
ColorComponent::AlphaOmitted => {
debug_assert!(false, "can't serialize an omitted alpha component");
},
}
Ok(())
}
}
impl<ValueType> ToAnimatedValue for ColorComponent<ValueType> {
type AnimatedValue = Self;
fn to_animated_value(self, _context: &crate::values::animated::Context) -> Self::AnimatedValue {
self
}
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
animated
}
}