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
/* 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 super::AbsoluteColor;
use crate::{
    parser::ParserContext,
    values::{
        generics::calc::CalcUnits,
        specified::calc::{CalcNode as SpecifiedCalcNode, Leaf as SpecifiedLeaf},
    },
};
use cssparser::{Parser, Token};
use style_traits::{ParseError, StyleParseErrorKind};

/// A single color component.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum ColorComponent<ValueType> {
    /// The "none" keyword.
    None,
    /// A absolute value.
    Value(ValueType),
}

impl<ValueType> ColorComponent<ValueType> {
    /// Return true if the component is "none".
    #[inline]
    pub fn is_none(&self) -> bool {
        matches!(self, Self::None)
    }

    /// If the component contains a value, map it to another value.
    pub fn map_value<OutType>(
        self,
        f: impl FnOnce(ValueType) -> OutType,
    ) -> ColorComponent<OutType> {
        match self {
            Self::None => ColorComponent::None,
            Self::Value(value) => ColorComponent::Value(f(value)),
        }
    }
    /// Return the component as its value.
    pub fn into_value(self) -> ValueType {
        match self {
            Self::None => panic!("value not available when component is None"),
            Self::Value(value) => value,
        }
    }

    /// Return the component as its value or a default value.
    pub fn into_value_or(self, default: ValueType) -> ValueType {
        match self {
            Self::None => default,
            Self::Value(value) => value,
        }
    }
}

/// An utility trait that allows the construction of [ColorComponent]
/// `ValueType`'s after parsing a color component.
pub trait ColorComponentType: Sized {
    // 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: &SpecifiedLeaf) -> 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,
        origin_color: Option<&AbsoluteColor>,
    ) -> 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) if origin_color.is_some() => {
                match origin_color
                    .unwrap()
                    .get_component_by_channel_keyword(ident)
                {
                    Ok(Some(value)) => Ok(Self::Value(ValueType::from_value(value))),
                    _ => Err(location.new_unexpected_token_error(t.clone())),
                }
            },
            Token::Function(ref name) => {
                let function = SpecifiedCalcNode::math_function(context, name, location)?;
                let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?;

                let Ok(resolved_leaf) = node.resolve() else {
                    return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
                };

                ValueType::try_from_leaf(&resolved_leaf)
                    .map(Self::Value)
                    .map_err(|_| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
            },
            ref t => ValueType::try_from_token(t)
                .map(Self::Value)
                .map_err(|_| location.new_unexpected_token_error(t.clone())),
        }
    }
}