style/values/computed/ratio.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//! `<ratio>` computed values.
6
7use crate::values::animated::{Animate, Procedure};
8use crate::values::computed::NonNegativeNumber;
9use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
10use crate::values::generics::ratio::Ratio as GenericRatio;
11use crate::Zero;
12use std::cmp::Ordering;
13
14/// A computed <ratio> value.
15pub type Ratio = GenericRatio<NonNegativeNumber>;
16
17impl PartialOrd for Ratio {
18 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
19 f64::partial_cmp(
20 &((self.0).0 as f64 * (other.1).0 as f64),
21 &((self.1).0 as f64 * (other.0).0 as f64),
22 )
23 }
24}
25
26impl GenericRatio<f32> {
27 /// Returns the f32 value by dividing the first value by the second one.
28 #[inline]
29 fn to_f32(&self) -> f32 {
30 debug_assert!(!self.is_degenerate());
31 self.0 / self.1
32 }
33}
34
35/// https://drafts.csswg.org/css-values/#combine-ratio
36impl Animate for GenericRatio<f32> {
37 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
38 // If either <ratio> is degenerate, the values cannot be interpolated.
39 if self.is_degenerate() || other.is_degenerate() {
40 return Err(());
41 }
42
43 // Addition of <ratio>s is not possible, and based on
44 // https://drafts.csswg.org/css-values-4/#not-additive,
45 // we simply use the first value as the result value.
46 // Besides, the procedure for accumulation should be identical to addition here.
47 if matches!(procedure, Procedure::Add | Procedure::Accumulate { .. }) {
48 return Ok(self.clone());
49 }
50
51 // The interpolation of a <ratio> is defined by converting each <ratio> to a number by
52 // dividing the first value by the second (so a ratio of 3 / 2 would become 1.5), taking
53 // the logarithm of that result (so the 1.5 would become approximately 0.176), then
54 // interpolating those values.
55 //
56 // The result during the interpolation is converted back to a <ratio> by inverting the
57 // logarithm, then interpreting the result as a <ratio> with the result as the first value
58 // and 1 as the second value.
59 let start = self.to_f32().ln();
60 let end = other.to_f32().ln();
61 let e = std::f32::consts::E;
62 let result = e.powf(start.animate(&end, procedure)?);
63 // The range of the result is [0, inf), based on the easing function.
64 if result.is_zero() || result.is_infinite() {
65 return Err(());
66 }
67 Ok(GenericRatio(result, 1.0))
68 }
69}
70
71impl ComputeSquaredDistance for GenericRatio<f32> {
72 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
73 if self.is_degenerate() || other.is_degenerate() {
74 return Err(());
75 }
76 // Use the distance of their logarithm values. (This is used by testing, so don't
77 // need to care about the base. Here we use the same base as that in animate().)
78 self.to_f32()
79 .ln()
80 .compute_squared_distance(&other.to_f32().ln())
81 }
82}
83
84impl Ratio {
85 /// Returns a new Ratio.
86 #[inline]
87 pub fn new(a: f32, b: f32) -> Self {
88 GenericRatio(a.into(), b.into())
89 }
90}