normalize_css_z/
lib.rs

1//! Normalize a CSS z-index to an f32 floating-point number between `0.0` and `1.0`. (not exactly...)
2//!
3//! Theoretically, this is not entirely feasible because we can generate only
4//! `2^23 (subnormal) * 127 (normal) = 1,065,353,216` distinct floating-point numbers in this range.
5//!
6//! And, the best part is that we only get a handful of precise numbers. Specifically, this crate
7//! can generate `25,165,825` unique floating-point numbers between `0.0` and `1.0`, which should be more
8//! than sufficient for most use cases (hopefully).
9//!
10//! ## Supported ranges of z-indexes (default)
11//!
12//! | LOWER                             | MIDDLE                   | UPPER                           |
13//! | --------------------------------- | ------------------------ | ------------------------------- |
14//! | `-2_147_483_647..=-2_139_095_039` | `-4_194_303..=4_194_304` | `2_139_095_040..=2_147_483_647` |
15//!
16//! # Example
17//!
18//! ```
19//! # use normalize_css_z::normalize;
20//! # fn main() {
21//! let z_ = 2_147_483_647;
22//! if let Some(z) = normalize(z_) {
23//!     // Do something with `z`.
24//! } else {
25//!     // Handle unsupported z-index.
26//! }
27//! # }
28//! ```
29//!
30//! Later, I aim to expand this to allow for customizable ranges, but for now, this should be adequate.
31
32#[cfg(feature = "custom")]
33pub mod normalizer;
34#[cfg(feature = "custom")]
35pub mod ranges;
36
37pub const MAX_CSS_Z: i32 = 2_147_483_647;
38pub const MANTISSA: i32 = 8_388_608;
39
40pub const RANGE_UPPER_U: i32 = MAX_CSS_Z;
41pub const RANGE_UPPER_L: i32 = MAX_CSS_Z - MANTISSA + 1;
42
43pub const RANGE_MIDDLE_U: i32 = MANTISSA / 2;
44pub const RANGE_MIDDLE_L: i32 = -MANTISSA / 2 + 1;
45
46pub const RANGE_LOWER_U: i32 = -MAX_CSS_Z + MANTISSA;
47pub const RANGE_LOWER_L: i32 = -MAX_CSS_Z;
48
49/// Normalizes a CSS z-index to an f32 floating-point number between 0.0 and 1.0.
50///
51/// This is the most hassle-free way to use this crate if you don't need to customize the ranges.
52///
53/// ```
54/// # use normalize_css_z::normalize;
55/// # fn main() {
56/// let z_ = 2_147_483_647;
57/// let z = normalize(z_);
58/// assert_eq!(z, Some(1.0));
59/// # }
60/// ```
61pub fn normalize(z: i32) -> Option<f32> {
62    fn helper(z_: i32, upper_bound: i32, exp_offset: i32) -> Option<f32> {
63        let z = upper_bound - z_;
64        let quo = z / MANTISSA;
65        let rem = z % MANTISSA;
66        let normal = 2f32.powi(-quo - exp_offset);
67
68        // Returns the `n`th subnormal number for the given `normal`.
69        Some(f32::from_bits(normal.to_bits() - rem as u32))
70    }
71
72    match z {
73        RANGE_LOWER_L => Some(0.0),
74        RANGE_UPPER_U => Some(1.0),
75        RANGE_LOWER_L..=RANGE_LOWER_U => helper(z, RANGE_LOWER_U, 2),
76        RANGE_MIDDLE_L..=RANGE_MIDDLE_U => helper(z, RANGE_MIDDLE_U, 1),
77        RANGE_UPPER_L..=RANGE_UPPER_U => helper(z, RANGE_UPPER_U, 0),
78        _ => None,
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_unsupported_z1() {
88        assert!(normalize(RANGE_UPPER_L - 1).is_none());
89    }
90    #[test]
91    fn test_unsupported_z2() {
92        assert!(normalize(RANGE_MIDDLE_U + 1).is_none());
93    }
94    #[test]
95    fn test_unsupported_z3() {
96        assert!(normalize(RANGE_MIDDLE_L - 1).is_none());
97    }
98    #[test]
99    fn test_unsupported_z4() {
100        assert!(normalize(RANGE_LOWER_L - 1).is_none());
101    }
102    #[test]
103    fn test_unsupported_z5() {
104        assert!(normalize(RANGE_LOWER_U + 1).is_none());
105    }
106
107    #[test]
108    fn test_supported_z1() {
109        assert!(normalize(RANGE_UPPER_U).is_some());
110    }
111    #[test]
112    fn test_supported_z2() {
113        assert!(normalize(RANGE_UPPER_L).is_some());
114    }
115    #[test]
116    fn test_supported_z3() {
117        assert!(normalize(RANGE_MIDDLE_L).is_some());
118    }
119    #[test]
120    fn test_supported_z4() {
121        assert!(normalize(RANGE_MIDDLE_U).is_some());
122    }
123    #[test]
124    fn test_supported_z5() {
125        assert!(normalize(RANGE_LOWER_L).is_some());
126    }
127    #[test]
128    fn test_supported_z6() {
129        assert!(normalize(RANGE_LOWER_U).is_some());
130    }
131
132    #[ignore]
133    #[test]
134    fn test_normalize_upper() {
135        let mut prev = -1.0;
136        let mut count = 0;
137        for i in RANGE_UPPER_L..=RANGE_UPPER_U {
138            count += 1;
139            let curr = normalize(i).unwrap();
140            assert!(curr > prev, "{i}: {} < {}", curr, prev);
141            prev = curr;
142        }
143        eprintln!("{count}");
144    }
145
146    #[ignore]
147    #[test]
148    fn test_normalize_middle() {
149        let mut prev = -1.0;
150        let mut count = 0;
151        for i in RANGE_MIDDLE_L..=RANGE_MIDDLE_U {
152            count += 1;
153            let curr = normalize(i).unwrap();
154            assert!(curr > prev, "{i}: {} < {}", curr, prev);
155            prev = curr;
156        }
157        eprintln!("{count}");
158    }
159
160    #[ignore]
161    #[test]
162    fn test_normalize_lower() {
163        let mut prev = -1.0;
164        let mut count = 0;
165        for i in RANGE_LOWER_L..=RANGE_LOWER_U {
166            count += 1;
167            let curr = normalize(i).unwrap();
168            assert!(curr > prev, "{i}: {} < {}", curr, prev);
169            prev = curr;
170        }
171        eprintln!("{count}");
172    }
173
174    #[ignore]
175    #[test]
176    fn test_continuity() {
177        let mut prev = -1.0;
178        let curr = normalize(RANGE_LOWER_U).unwrap();
179        assert!(curr > prev, "{}: {} > {}", RANGE_LOWER_U, curr, prev);
180        eprintln!("(LU): {curr} : {}", RANGE_LOWER_U);
181        prev = curr;
182        let curr = normalize(RANGE_MIDDLE_L).unwrap();
183        assert!(curr > prev, "{}: {} > {}", RANGE_MIDDLE_L, curr, prev);
184        eprintln!("(ML): {curr} : {}", RANGE_MIDDLE_L);
185        prev = curr;
186        let curr = normalize(RANGE_MIDDLE_U).unwrap();
187        assert!(curr > prev, "{}: {} > {}", RANGE_MIDDLE_U, curr, prev);
188        eprintln!("(MU): {curr} : {}", RANGE_MIDDLE_U);
189        prev = curr;
190        let curr = normalize(RANGE_UPPER_L).unwrap();
191        assert!(curr > prev, "{}: {} > {}", RANGE_UPPER_L, curr, prev);
192        eprintln!("(UL): {curr} : {}", RANGE_UPPER_L);
193    }
194
195    #[test]
196    fn test_normalize_all() {
197        test_normalize_lower();
198        test_normalize_middle();
199        test_normalize_upper();
200        test_continuity();
201    }
202}