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