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