css_colors/
ratio.rs

1use std::fmt;
2use std::ops;
3
4/// Construct an ratio from percentages. Values outside of the 0-100% range
5/// will cause a panic.
6///
7/// # Example
8/// ```
9/// use css_colors::{percent};
10///
11/// assert_eq!(percent(0).to_string(), "0%");
12/// assert_eq!(percent(25).to_string(), "25%");
13/// assert_eq!(percent(100).to_string(), "100%");
14/// ```
15pub fn percent(percentage: u8) -> Ratio {
16    Ratio::from_percentage(percentage)
17}
18
19#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
20/// A struct that represents a ratio and determines the legal value(s) for a given type.
21/// Clamps any values that fall beyond the valid legal range for the type.
22/// Used to convert a type into a valid percentage representation.
23pub struct Ratio(u8);
24
25impl Ratio {
26    pub fn from_percentage(percentage: u8) -> Self {
27        assert!(percentage <= 100, "Invalid value for percentage");
28
29        Ratio::from_f32(percentage as f32 / 100.0)
30    }
31
32    pub fn from_u8(value: u8) -> Self {
33        Ratio(value)
34    }
35
36    pub fn from_f32(float: f32) -> Self {
37        assert!(float >= 0.0, "Invalid ratio for type f32");
38        assert!(float <= 1.0, "Invalid ratio for type f32");
39
40        Ratio((float * 255.0).round() as u8)
41    }
42
43    pub fn as_percentage(self) -> u8 {
44        (self.0 as f32 / 255.0 * 100.0).round() as u8
45    }
46
47    pub fn as_u8(self) -> u8 {
48        self.0
49    }
50
51    pub fn as_f32(self) -> f32 {
52        self.0 as f32 / 255.0
53    }
54}
55
56impl fmt::Display for Ratio {
57    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58        write!(f, "{}%", self.as_percentage())
59    }
60}
61
62impl ops::Add for Ratio {
63    type Output = Ratio;
64
65    fn add(self, other: Ratio) -> Ratio {
66        clamp_ratio(self.as_f32() + other.as_f32())
67    }
68}
69
70impl ops::Sub for Ratio {
71    type Output = Ratio;
72
73    fn sub(self, other: Ratio) -> Ratio {
74        clamp_ratio(self.as_f32() - other.as_f32())
75    }
76}
77
78impl ops::Mul for Ratio {
79    type Output = Ratio;
80
81    fn mul(self, other: Ratio) -> Ratio {
82        clamp_ratio(self.as_f32() * other.as_f32())
83    }
84}
85
86impl ops::Div for Ratio {
87    type Output = Ratio;
88
89    fn div(self, other: Ratio) -> Ratio {
90        clamp_ratio(self.as_f32() / other.as_f32())
91    }
92}
93
94// A function to clamp the value of a Ratio to fall between [0.0 - 1.0].
95fn clamp_ratio(value: f32) -> Ratio {
96    if value > 1.0 {
97        Ratio::from_f32(1.0)
98    } else if value >= 0.0 && value <= 1.0 {
99        Ratio::from_f32(value)
100    } else {
101        Ratio::from_f32(0.0)
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use Ratio;
108
109    #[test]
110    #[should_panic]
111    fn handles_invalid_percentage() {
112        Ratio::from_percentage(101);
113    }
114
115    #[test]
116    #[should_panic]
117    fn handles_invalid_f32() {
118        Ratio::from_f32(1.01);
119    }
120
121    #[test]
122    fn can_clamp_percentage() {
123        assert_eq!(
124            Ratio::from_percentage(50) + Ratio::from_percentage(55),
125            Ratio::from_percentage(100)
126        );
127        assert_eq!(
128            Ratio::from_percentage(50) - Ratio::from_percentage(55),
129            Ratio::from_percentage(0)
130        );
131        assert_eq!(
132            Ratio::from_percentage(55) / Ratio::from_percentage(50),
133            Ratio::from_percentage(100)
134        );
135    }
136
137    #[test]
138    fn can_clamp_f32() {
139        assert_eq!(
140            Ratio::from_f32(0.75) + Ratio::from_f32(0.75),
141            Ratio::from_f32(1.0)
142        );
143        assert_eq!(
144            Ratio::from_f32(0.25) - Ratio::from_f32(0.75),
145            Ratio::from_f32(0.0)
146        );
147        assert_eq!(
148            Ratio::from_f32(0.75) / Ratio::from_f32(0.25),
149            Ratio::from_f32(1.0)
150        );
151    }
152
153    #[test]
154    fn adds_percentage() {
155        let a = Ratio::from_percentage(55);
156        let b = Ratio::from_percentage(45);
157        let c = Ratio::from_percentage(10);
158
159        assert_eq!(a + b, Ratio::from_percentage(100));
160        assert_eq!(a + c, Ratio::from_percentage(65));
161    }
162
163    #[test]
164    fn subtracts_percentage() {
165        let a = Ratio::from_percentage(45);
166        let b = Ratio::from_percentage(10);
167        let c = Ratio::from_percentage(1);
168
169        assert_eq!(a - b, Ratio::from_percentage(35));
170        assert_eq!(b - c, Ratio::from_percentage(9));
171    }
172
173    #[test]
174    fn multiplies_percentage() {
175        let a = Ratio::from_percentage(100);
176        let b = Ratio::from_percentage(50);
177        let c = Ratio::from_percentage(20);
178
179        assert_eq!(a * a, Ratio::from_percentage(100));
180        assert_eq!(b * b, Ratio::from_percentage(25));
181        assert_eq!(c * c, Ratio::from_percentage(4));
182
183        assert_eq!(a * b, Ratio::from_percentage(50));
184        assert_eq!(b * a, Ratio::from_percentage(50));
185
186        assert_eq!(a * c, Ratio::from_percentage(20));
187        assert_eq!(c * a, Ratio::from_percentage(20));
188
189        assert_eq!(b * c, Ratio::from_percentage(10));
190        assert_eq!(c * b, Ratio::from_percentage(10));
191    }
192
193    #[test]
194    fn divides_percentage() {
195        let a = Ratio::from_percentage(100);
196        let b = Ratio::from_percentage(50);
197        let c = Ratio::from_percentage(20);
198
199        assert_eq!(a / a, Ratio::from_percentage(100));
200        assert_eq!(b / b, Ratio::from_percentage(100));
201        assert_eq!(c / c, Ratio::from_percentage(100));
202
203        assert_eq!(b / a, Ratio::from_percentage(50));
204        assert_eq!(c / a, Ratio::from_percentage(20));
205        assert_eq!(c / b, Ratio::from_percentage(40));
206    }
207
208    #[test]
209    fn adds_f32() {
210        let a = Ratio::from_f32(0.55);
211        let b = Ratio::from_f32(0.45);
212        let c = Ratio::from_f32(0.10);
213
214        assert_eq!(a + b, Ratio::from_f32(1.0));
215        assert_eq!(c + c, Ratio::from_u8(52));
216        assert_eq!(b + c, Ratio::from_u8(141));
217    }
218
219    #[test]
220    fn subtracts_f32() {
221        let a = Ratio::from_f32(0.55);
222        let b = Ratio::from_f32(0.45);
223        let c = Ratio::from_f32(0.10);
224
225        assert_eq!(b - c, Ratio::from_f32(0.35));
226        assert_eq!(a - b, Ratio::from_u8(25));
227        assert_eq!(a - c, Ratio::from_u8(114));
228    }
229
230    #[test]
231    fn multiplies_f32() {
232        let a = Ratio::from_f32(0.5);
233        let b = Ratio::from_f32(0.25);
234
235        assert_eq!(a * b, Ratio::from_f32(0.125));
236        assert_eq!(a * a, Ratio::from_f32(0.25));
237        assert_eq!(b * b, Ratio::from_f32(0.0625));
238    }
239
240    #[test]
241    fn divides_f32() {
242        let a = Ratio::from_f32(0.25);
243        let b = Ratio::from_f32(0.50);
244        let c = Ratio::from_f32(1.00);
245
246        assert_eq!(a / b, Ratio::from_f32(0.5));
247        assert_eq!(a / c, Ratio::from_f32(0.25));
248        assert_eq!(b / c, Ratio::from_f32(0.5));
249    }
250}