tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }