Skip to main content

style/values/computed/
calc.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//! Computed-value calc() leaf types.
6
7use super::{Angle, Length, Number, Percentage, Resolution, Time};
8use crate::derives::*;
9use crate::typed_om::{NumericBaseType, NumericType};
10use crate::values::generics::calc::{
11    self, CalcType, GenericCalcPercentageLeaf, SimplificationResult,
12};
13use crate::values::generics::Optional;
14use crate::Zero;
15use debug_unreachable::debug_unreachable;
16use serde::{Deserialize, Serialize};
17
18/// The value of a percentage leaf node that contains an associated percent hint.
19pub type CalcPercentageLeaf = GenericCalcPercentageLeaf<Percentage>;
20
21/// The computed leaf of a calc() expression.
22#[derive(
23    Clone,
24    Debug,
25    Deserialize,
26    MallocSizeOf,
27    PartialEq,
28    Serialize,
29    ToAnimatedZero,
30    ToCss,
31    ToResolvedValue,
32    ToTyped,
33)]
34#[allow(missing_docs)]
35#[repr(u8)]
36pub enum ComputedLeaf {
37    Length(Length),
38    Percentage(CalcPercentageLeaf),
39    Number(Number),
40    Angle(Angle),
41    Time(Time),
42    Resolution(Resolution),
43}
44
45impl ComputedLeaf {
46    pub(super) fn is_zero_length(&self) -> bool {
47        match *self {
48            Self::Length(ref l) => l.is_zero(),
49            Self::Percentage(..)
50            | Self::Number(..)
51            | Self::Angle(..)
52            | Self::Time(..)
53            | Self::Resolution(..) => false,
54        }
55    }
56}
57
58impl calc::CalcNodeLeaf for ComputedLeaf {
59    fn numeric_type(&self) -> NumericType {
60        match self {
61            Self::Length(_) => NumericType::length(),
62            Self::Percentage(p) => p.numeric_type(),
63            Self::Number(_) => NumericType::number(),
64            Self::Angle(_) => NumericType::angle(),
65            Self::Time(_) => NumericType::time(),
66            Self::Resolution(_) => NumericType::resolution(),
67        }
68    }
69
70    fn unitless_value(&self) -> Option<f32> {
71        Some(match *self {
72            Self::Length(ref l) => l.px(),
73            Self::Percentage(ref p) => p.get(),
74            Self::Number(n) => n,
75            Self::Angle(ref a) => a.degrees(),
76            Self::Time(ref t) => t.seconds(),
77            Self::Resolution(ref r) => r.dppx(),
78        })
79    }
80
81    fn canonical_value(&self) -> Option<f32> {
82        Some(match *self {
83            Self::Length(ref l) => l.px(),
84            Self::Percentage(ref p) => match p.hint {
85                // Percentages that are relative to some other value (indicated by a
86                // percent hint other than "percent") cannot yet resolve to a numeric
87                // value, as the percentage's basis is not available.
88                Optional::Some(NumericBaseType::Percent) => p.get(),
89                _ => return None,
90            },
91            Self::Number(n) => n,
92            Self::Angle(ref a) => a.degrees(),
93            Self::Time(ref t) => t.seconds(),
94            Self::Resolution(ref r) => r.dppx(),
95        })
96    }
97
98    fn new_number(value: f32) -> Self {
99        Self::Number(value)
100    }
101
102    fn as_number(&self) -> Option<f32> {
103        match *self {
104            Self::Length(_)
105            | Self::Percentage(_)
106            | Self::Angle(_)
107            | Self::Time(_)
108            | Self::Resolution(_) => None,
109            Self::Number(value) => Some(value),
110        }
111    }
112
113    fn as_percentage(&self) -> Option<(f32, Optional<NumericBaseType>)> {
114        match *self {
115            Self::Percentage(p) => Some((p.get(), p.hint)),
116            _ => None,
117        }
118    }
119
120    fn as_angle_radians(&self) -> Option<f32> {
121        match *self {
122            Self::Angle(a) => Some(a.radians()),
123            _ => None,
124        }
125    }
126
127    fn new_angle_from_radians(radians: f32) -> Self {
128        Self::Angle(Angle::from_radians(radians))
129    }
130
131    fn new_from_typed_value(value: f32, numeric_type: NumericType) -> Result<Self, ()> {
132        let calc_type = numeric_type.as_calc_type()?;
133        let percent_hint = numeric_type.percent_hint();
134        Ok(match calc_type {
135            CalcType::Number => Self::new_number(value),
136            CalcType::Length => Self::Length(Length::new(value)),
137            CalcType::Angle => Self::Angle(Angle::from_degrees(value)),
138            CalcType::Time => Self::Time(Time::from_seconds(value)),
139            CalcType::Resolution => Self::Resolution(Resolution::from_dppx(value)),
140            CalcType::Percentage => Self::Percentage(CalcPercentageLeaf::new(value, percent_hint)),
141        })
142    }
143
144    fn compare(&self, other: &Self) -> Option<std::cmp::Ordering> {
145        use self::ComputedLeaf::*;
146        if std::mem::discriminant(self) != std::mem::discriminant(other) {
147            return None;
148        }
149
150        // Percentages that resolve against some other basis value cannot be meaningfully compared.
151        if matches!(self, Percentage(p) if p.hint != Optional::Some(NumericBaseType::Percent)) {
152            return None;
153        }
154
155        let Ok(self_negative) = self.is_negative() else {
156            return None;
157        };
158        let Ok(other_negative) = other.is_negative() else {
159            return None;
160        };
161        if self_negative != other_negative {
162            return Some(if self_negative {
163                std::cmp::Ordering::Less
164            } else {
165                std::cmp::Ordering::Greater
166            });
167        }
168
169        match (self, other) {
170            (Length(one), Length(other)) => one.partial_cmp(other),
171            (Percentage(one), Percentage(other)) => one.value.partial_cmp(&other.value),
172            (Number(one), Number(other)) => one.partial_cmp(other),
173            (Angle(one), Angle(other)) => one.partial_cmp(other),
174            (Time(one), Time(other)) => one.partial_cmp(other),
175            (Resolution(one), Resolution(other)) => one.partial_cmp(other),
176            _ => unsafe {
177                match *self {
178                    Length(..) | Percentage(..) | Number(..) | Angle(..) | Time(..)
179                    | Resolution(..) => {},
180                }
181                debug_unreachable!("Forgot to handle unit in compare()")
182            },
183        }
184    }
185
186    fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
187        use self::ComputedLeaf::*;
188
189        // 0px plus anything else is equal to the right hand side.
190        if self.is_zero_length() {
191            *self = other.clone();
192            return Ok(());
193        }
194
195        if other.is_zero_length() {
196            return Ok(());
197        }
198
199        if std::mem::discriminant(self) != std::mem::discriminant(other) {
200            return Err(());
201        }
202
203        match (self, other) {
204            (&mut Length(ref mut one), Length(other)) => {
205                *one += *other;
206            },
207            (&mut Percentage(ref mut one), Percentage(other)) => {
208                *one = CalcPercentageLeaf::new(one.get() + other.get(), one.combined_hint(other));
209            },
210            (&mut Number(ref mut one), Number(other)) => {
211                *one += *other;
212            },
213            (&mut Angle(ref mut one), Angle(other)) => {
214                *one += *other;
215            },
216            (&mut Time(ref mut one), Time(other)) => {
217                *one += *other;
218            },
219            (&mut Resolution(ref mut one), Resolution(other)) => {
220                *one += *other;
221            },
222            _ => unsafe {
223                match *other {
224                    Length(..) | Percentage(..) | Number(..) | Angle(..) | Time(..)
225                    | Resolution(..) => {},
226                }
227                debug_unreachable!("Forgot to handle unit in try_sum_in_place()")
228            },
229        }
230
231        Ok(())
232    }
233
234    fn try_product_in_place(&mut self, other: &mut Self) -> bool {
235        if let Self::Number(ref mut left) = *self {
236            if let Self::Number(ref right) = *other {
237                // Both sides are numbers, so we can just modify the left side.
238                *left *= *right;
239                true
240            } else {
241                // The right side is not a number, so the result should be in the units of the right
242                // side.
243                if other.map(|v| v * *left).is_ok() {
244                    std::mem::swap(self, other);
245                    true
246                } else {
247                    false
248                }
249            }
250        } else if let Self::Number(ref right) = *other {
251            // The left side is not a number, but the right side is, so the result is the left
252            // side unit.
253            self.map(|v| v * *right).is_ok()
254        } else {
255            // Neither side is a number, so a product is not possible.
256            false
257        }
258    }
259
260    fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
261    where
262        O: Fn(f32, f32) -> f32,
263    {
264        use self::ComputedLeaf::*;
265        if std::mem::discriminant(self) != std::mem::discriminant(other) {
266            return Err(());
267        }
268        Ok(match (self, other) {
269            (Length(one), Length(other)) => Length(super::Length::new(op(one.px(), other.px()))),
270            (Percentage(one), Percentage(other)) => Self::Percentage(CalcPercentageLeaf::new(
271                op(one.get(), other.get()),
272                one.combined_hint(other),
273            )),
274            (&Number(one), &Number(other)) => Self::Number(op(one, other)),
275            (Angle(one), Angle(other)) => Self::Angle(super::Angle::from_degrees(op(
276                one.degrees(),
277                other.degrees(),
278            ))),
279            (Time(one), Time(other)) => Self::Time(super::Time::from_seconds(op(
280                one.seconds(),
281                other.seconds(),
282            ))),
283            (Resolution(one), Resolution(other)) => {
284                Self::Resolution(super::Resolution::from_dppx(op(one.dppx(), other.dppx())))
285            },
286            _ => unsafe {
287                match *self {
288                    Length(..) | Percentage(..) | Number(..) | Angle(..) | Time(..)
289                    | Resolution(..) => {},
290                }
291                debug_unreachable!("Forgot to handle unit in try_op()")
292            },
293        })
294    }
295
296    fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
297        let _: () = match self {
298            Self::Length(value) => {
299                *value = Length::new(op(value.px()));
300            },
301            Self::Percentage(p) => {
302                *p = CalcPercentageLeaf::new(op(p.get()), p.hint);
303            },
304            Self::Number(value) => {
305                *value = op(*value);
306            },
307            Self::Angle(value) => {
308                *value = Angle::from_degrees(op(value.degrees()));
309            },
310            Self::Time(value) => {
311                *value = Time::from_seconds(op(value.seconds()));
312            },
313            Self::Resolution(value) => {
314                *value = Resolution::from_dppx(op(value.dppx()));
315            },
316        };
317        Ok(())
318    }
319
320    fn simplify(&mut self) -> SimplificationResult {
321        SimplificationResult::Unchanged
322    }
323
324    fn sort_key(&self) -> calc::SortKey {
325        match *self {
326            Self::Length(..) => calc::SortKey::Px,
327            Self::Percentage(..) => calc::SortKey::Percentage,
328            Self::Number(..) => calc::SortKey::Number,
329            Self::Angle(..) => calc::SortKey::Deg,
330            Self::Time(..) => calc::SortKey::S,
331            Self::Resolution(..) => calc::SortKey::Dppx,
332        }
333    }
334}