style/typed_om/
sum_value.rs1use crate::typed_om::numeric::NoCalcNumeric;
8use crate::typed_om::numeric_type::NumericType;
9use crate::typed_om::{MathSum, MathValue, NumericValue, UnitValue};
10use itertools::Itertools;
11use std::collections::HashMap;
12use style_traits::CssString;
13use thin_vec::ThinVec;
14
15type UnitMap = HashMap<String, i32>;
16
17fn product_of_two_unit_maps(s: &UnitMap, other: &UnitMap) -> UnitMap {
19 let mut result = s.clone();
21
22 for (unit, power) in other {
24 *result.entry(unit.clone()).or_insert(0) += power;
26 }
27
28 result
30}
31
32#[derive(Clone, Debug)]
34struct SumValueItem {
35 value: f32,
36 unit_map: UnitMap,
37}
38
39impl SumValueItem {
40 fn to_unit_value(&self) -> Result<UnitValue, ()> {
42 if self.unit_map.len() > 1 {
44 return Err(());
45 }
46
47 if self.unit_map.is_empty() {
49 return Ok(UnitValue {
50 numeric_type: NumericType::number(),
51 value: self.value,
52 unit: CssString::from("number"),
53 });
54 }
55
56 let (unit, power) = self.unit_map.iter().next().unwrap();
58 if *power != 1 {
59 return Err(());
60 }
61
62 Ok(UnitValue {
64 numeric_type: NumericType::from_unit_unchecked(unit),
65 value: self.value,
66 unit: CssString::from(unit),
67 })
68 }
69}
70
71#[derive(Clone, Debug)]
73pub struct SumValue(Vec<SumValueItem>);
74
75impl SumValue {
76 pub fn try_from_numeric_value(value: &NumericValue) -> Result<Self, ()> {
78 match value {
79 NumericValue::Unit(unit_value) => {
81 let mut value = unit_value.value;
83 let mut unit = unit_value.unit_str();
84
85 let numeric = NoCalcNumeric::parse_unit_value(value, unit)?;
87 if let Some(canonical_unit) = numeric.canonical_unit() {
88 let canonical = numeric.to(canonical_unit)?;
89 value = canonical.unitless_value();
90 unit = canonical.unit();
91 }
92
93 if unit.eq_ignore_ascii_case("number") {
95 return Ok(Self(vec![SumValueItem {
96 value,
97 unit_map: UnitMap::new(),
98 }]));
99 }
100
101 Ok(Self(vec![SumValueItem {
103 value,
104 unit_map: [(unit.to_owned(), 1)].into_iter().collect::<UnitMap>(),
105 }]))
106 },
107
108 NumericValue::Math(MathValue::Sum(math_sum)) => {
110 let mut values: Vec<SumValueItem> = Vec::new();
112
113 for item in &math_sum.values {
115 let value = SumValue::try_from_numeric_value(item)?;
117
118 for sub_value in value.0 {
120 if let Some(item) = values
122 .iter_mut()
123 .find(|item| item.unit_map == sub_value.unit_map)
124 {
125 item.value += sub_value.value;
126 continue;
127 }
128
129 values.push(sub_value);
131 }
132 }
133
134 Ok(Self(values))
145 },
146
147 NumericValue::Math(MathValue::Product(math_product)) => {
149 let mut values = vec![SumValueItem {
151 value: 1.0,
152 unit_map: Default::default(),
153 }];
154
155 for item in math_product {
157 let new_values = SumValue::try_from_numeric_value(item)?;
159
160 let mut temp = Vec::new();
161
162 for item1 in &values {
164 for item2 in &new_values.0 {
166 let mut unit_map =
168 product_of_two_unit_maps(&item1.unit_map, &item2.unit_map);
169 unit_map.retain(|_, power| *power != 0);
170 let item = SumValueItem {
171 value: item1.value * item2.value,
172 unit_map,
173 };
174
175 temp.push(item);
177 }
178 }
179
180 values = temp;
182 }
183
184 Ok(Self(values))
186 },
187
188 NumericValue::Math(MathValue::Negate(math_negate)) => {
190 let mut values = SumValue::try_from_numeric_value(math_negate)?.0;
192
193 for item in &mut values {
195 item.value = -item.value;
196 }
197
198 Ok(Self(values))
200 },
201
202 NumericValue::Math(MathValue::Invert(math_invert)) => {
204 let mut values = SumValue::try_from_numeric_value(math_invert)?.0;
206
207 if values.len() != 1 {
209 return Err(());
210 }
211
212 let item = &mut values[0];
213
214 item.value = 1.0 / item.value;
216 for power in item.unit_map.values_mut() {
217 *power = -*power;
218 }
219
220 Ok(Self(values))
222 },
223
224 NumericValue::Math(MathValue::Min(math_min)) => {
226 let mut args = Vec::new();
228
229 for item in math_min {
230 let values = SumValue::try_from_numeric_value(item)?;
231
232 if values.0.len() > 1 {
233 return Err(());
234 }
235
236 args.push(values);
237 }
238
239 debug_assert!(!args.is_empty());
240
241 if !args.iter().map(|arg| &arg.0[0].unit_map).all_equal() {
243 return Err(());
244 }
245
246 let min = args
248 .into_iter()
249 .map(|arg| arg.0.into_iter().next().unwrap())
250 .min_by(|a, b| a.value.total_cmp(&b.value))
251 .ok_or(())?;
252
253 Ok(Self(vec![min]))
254 },
255
256 NumericValue::Math(MathValue::Max(math_max)) => {
258 let mut args = Vec::new();
260
261 for item in math_max {
262 let values = SumValue::try_from_numeric_value(item)?;
263
264 if values.0.len() > 1 {
265 return Err(());
266 }
267
268 args.push(values);
269 }
270 debug_assert!(!args.is_empty());
271
272 if !args.iter().map(|arg| &arg.0[0].unit_map).all_equal() {
274 return Err(());
275 }
276
277 let max = args
279 .into_iter()
280 .map(|arg| arg.0.into_iter().next().unwrap())
281 .max_by(|a, b| a.value.total_cmp(&b.value))
282 .ok_or(())?;
283
284 Ok(Self(vec![max]))
285 },
286
287 NumericValue::Math(MathValue::Clamp(math_clamp)) => {
308 let lower = SumValue::try_from_numeric_value(&math_clamp[0])?;
310 let value = SumValue::try_from_numeric_value(&math_clamp[1])?;
311 let upper = SumValue::try_from_numeric_value(&math_clamp[2])?;
312
313 if lower.0.len() > 1 || value.0.len() > 1 || upper.0.len() > 1 {
314 return Err(());
315 }
316
317 if lower.0[0].unit_map != value.0[0].unit_map
319 || lower.0[0].unit_map != upper.0[0].unit_map
320 {
321 return Err(());
322 }
323
324 let mut value = value.0.into_iter().next().unwrap();
326 value.value = value.value.max(lower.0[0].value).min(upper.0[0].value);
327
328 Ok(Self(vec![value]))
329 },
330 }
331 }
332
333 pub fn to_unit(&self, unit: &str) -> Result<UnitValue, ()> {
336 debug_assert!(NumericType::try_from_unit(unit).is_ok());
337
338 if self.0.len() != 1 {
339 return Err(());
340 }
341
342 let sole_item = &self.0[0];
343
344 let item = sole_item.to_unit_value()?;
345
346 let item = {
347 let numeric = NoCalcNumeric::parse_unit_value(item.value, item.unit_str())?;
348 let converted = numeric.to(unit)?;
349
350 UnitValue {
351 numeric_type: NumericType::from_unit_unchecked(converted.unit()),
352 value: converted.unitless_value(),
353 unit: CssString::from(converted.unit()),
354 }
355 };
356
357 Ok(item)
358 }
359
360 pub fn to_units(&self, units: &[&str]) -> Result<MathSum, ()> {
363 debug_assert!(units
364 .iter()
365 .all(|unit| NumericType::try_from_unit(unit).is_ok()));
366
367 let mut values = self
369 .0
370 .iter()
371 .map(|item| item.to_unit_value())
372 .collect::<Result<Vec<_>, _>>()?;
373
374 if units.is_empty() {
376 values.sort_by(|a, b| a.unit.cmp(&b.unit));
377
378 return Ok(MathSum::from_numeric_values_unchecked(
379 values.into_iter().map(NumericValue::Unit).collect(),
380 ));
381 }
382
383 let mut result = ThinVec::new();
385
386 for unit in units {
387 let mut temp = UnitValue {
389 numeric_type: NumericType::from_unit_unchecked(*unit),
390 value: 0.0,
391 unit: CssString::from(*unit),
392 };
393
394 let mut i = 0;
396 while i < values.len() {
397 let value = &values[i];
398
399 let value_unit = value.unit_str();
401
402 let numeric = NoCalcNumeric::parse_unit_value(value.value, &value_unit)?;
404 if let Ok(converted) = numeric.to(unit) {
405 temp.value += converted.unitless_value();
407
408 values.remove(i);
410 } else {
411 i += 1;
412 }
413 }
414
415 result.push(NumericValue::Unit(temp));
417 }
418
419 if !values.is_empty() {
421 return Err(());
422 }
423
424 Ok(MathSum::from_numeric_values_unchecked(result))
425 }
426}