Skip to main content

style/typed_om/
sum_value.rs

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//! Typed OM Sum Value.
6
7use crate::typed_om::numeric_values::NoCalcNumeric;
8use std::collections::HashMap;
9use style_traits::{CssString, NumericValue, UnitValue};
10
11type UnitMap = HashMap<String, i32>;
12
13/// <https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-sum-value>
14#[derive(Clone, Debug)]
15struct SumValueItem {
16    value: f32,
17    unit_map: UnitMap,
18}
19
20impl SumValueItem {
21    /// <https://drafts.css-houdini.org/css-typed-om-1/#create-a-cssunitvalue-from-a-sum-value-item>
22    fn to_unit_value(&self) -> Result<UnitValue, ()> {
23        // Step 1.
24        if self.unit_map.len() > 1 {
25            return Err(());
26        }
27
28        // Step 2.
29        if self.unit_map.is_empty() {
30            return Ok(UnitValue {
31                value: self.value,
32                unit: CssString::from("number"),
33            });
34        }
35
36        // Step 3.
37        let (unit, power) = self.unit_map.iter().next().unwrap();
38        if *power != 1 {
39            return Err(());
40        }
41
42        // Step 4.
43        Ok(UnitValue {
44            value: self.value,
45            unit: CssString::from(unit),
46        })
47    }
48}
49
50/// <https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-sum-value>
51#[derive(Clone, Debug)]
52pub struct SumValue(Vec<SumValueItem>);
53
54impl SumValue {
55    /// <https://drafts.css-houdini.org/css-typed-om-1/#create-a-sum-value>
56    pub fn try_from_numeric_value(value: &NumericValue) -> Result<Self, ()> {
57        match value {
58            // CSSUnitValue
59            NumericValue::Unit(unit_value) => {
60                // Step 1.
61                let mut value = unit_value.value;
62                let mut unit = unit_value.unit.to_string();
63
64                // Step2.
65                let numeric = NoCalcNumeric::parse_unit_value(value, unit.as_str())?;
66                if let Some(canonical_unit) = numeric.canonical_unit() {
67                    let canonical = numeric.to(canonical_unit)?;
68                    value = canonical.unitless_value();
69                    unit = canonical.unit().to_string();
70                }
71
72                // Step 3.
73                if unit.eq_ignore_ascii_case("number") {
74                    return Ok(Self(vec![SumValueItem {
75                        value,
76                        unit_map: UnitMap::new(),
77                    }]));
78                }
79
80                // Step 4.
81                Ok(Self(vec![SumValueItem {
82                    value,
83                    unit_map: [(unit, 1)].into_iter().collect::<UnitMap>(),
84                }]))
85            },
86
87            // CSSMathSum
88            NumericValue::Sum(math_sum) => {
89                // Step 1.
90                let mut values: Vec<SumValueItem> = Vec::new();
91
92                // Step 2.
93                for item in &math_sum.values {
94                    // Step 2.1.
95                    let value = SumValue::try_from_numeric_value(item)?;
96
97                    // Step 2.2.
98                    for sub_value in value.0 {
99                        // Step 2.2.1.
100                        if let Some(item) = values
101                            .iter_mut()
102                            .find(|item| item.unit_map == sub_value.unit_map)
103                        {
104                            item.value += sub_value.value;
105                            continue;
106                        }
107
108                        // Step 2.2.2.
109                        values.push(sub_value);
110                    }
111                }
112
113                // Step 3.
114
115                // TODO: Create a type [1] from the unit map of each item of
116                // values, and add [2] all the types together. If the result is
117                // failure, return failure.
118                //
119                // [1] https://drafts.css-houdini.org/css-typed-om-1/#create-a-type-from-a-unit-map
120                // [2] https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-add-two-types
121
122                // Step 4.
123                Ok(SumValue(values))
124            },
125        }
126    }
127
128    /// Step 3 of:
129    /// https://drafts.css-houdini.org/css-typed-om-1/#dom-cssnumericvalue-to
130    pub fn resolve_to_unit(&self, unit: &str) -> Result<UnitValue, ()> {
131        if self.0.len() != 1 {
132            return Err(());
133        }
134
135        let sole_item = &self.0[0];
136
137        let item = sole_item.to_unit_value()?;
138
139        let item = {
140            let numeric =
141                NoCalcNumeric::parse_unit_value(item.value, item.unit.to_string().as_str())?;
142            let converted = numeric.to(unit)?;
143
144            UnitValue {
145                value: converted.unitless_value(),
146                unit: CssString::from(converted.unit()),
147            }
148        };
149
150        Ok(item)
151    }
152}