Skip to main content

use_font_size/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive font-size conversions.
3//!
4//! These helpers keep size conversions explicit and validate the input sizes
5//! used in the calculation.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_font_size::{FontSize, em_to_px, px_to_rem, rem_to_px};
11//!
12//! let font = FontSize::new(18.0).unwrap();
13//!
14//! assert!((font.rem(16.0).unwrap() - 1.125).abs() < 1.0e-12);
15//! assert!((px_to_rem(18.0, 16.0).unwrap() - 1.125).abs() < 1.0e-12);
16//! assert!((rem_to_px(1.125, 16.0).unwrap() - 18.0).abs() < 1.0e-12);
17//! assert!((em_to_px(1.5, 12.0).unwrap() - 18.0).abs() < 1.0e-12);
18//! ```
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct FontSize {
22    px: f64,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum FontSizeError {
27    InvalidFontSize,
28    InvalidRootSize,
29    InvalidParentSize,
30    InvalidRem,
31    InvalidEm,
32}
33
34fn validate_positive(value: f64, error: FontSizeError) -> Result<f64, FontSizeError> {
35    if !value.is_finite() || value <= 0.0 {
36        Err(error)
37    } else {
38        Ok(value)
39    }
40}
41
42impl FontSize {
43    pub fn new(px: f64) -> Result<Self, FontSizeError> {
44        Ok(Self {
45            px: validate_positive(px, FontSizeError::InvalidFontSize)?,
46        })
47    }
48
49    #[must_use]
50    pub fn px(&self) -> f64 {
51        self.px
52    }
53
54    pub fn rem(&self, root_px: f64) -> Result<f64, FontSizeError> {
55        px_to_rem(self.px, root_px)
56    }
57
58    pub fn em(&self, parent_px: f64) -> Result<f64, FontSizeError> {
59        px_to_em(self.px, parent_px)
60    }
61}
62
63pub fn px_to_rem(px: f64, root_px: f64) -> Result<f64, FontSizeError> {
64    Ok(validate_positive(px, FontSizeError::InvalidFontSize)?
65        / validate_positive(root_px, FontSizeError::InvalidRootSize)?)
66}
67
68pub fn rem_to_px(rem: f64, root_px: f64) -> Result<f64, FontSizeError> {
69    Ok(validate_positive(rem, FontSizeError::InvalidRem)?
70        * validate_positive(root_px, FontSizeError::InvalidRootSize)?)
71}
72
73pub fn px_to_em(px: f64, parent_px: f64) -> Result<f64, FontSizeError> {
74    Ok(validate_positive(px, FontSizeError::InvalidFontSize)?
75        / validate_positive(parent_px, FontSizeError::InvalidParentSize)?)
76}
77
78pub fn em_to_px(em: f64, parent_px: f64) -> Result<f64, FontSizeError> {
79    Ok(validate_positive(em, FontSizeError::InvalidEm)?
80        * validate_positive(parent_px, FontSizeError::InvalidParentSize)?)
81}
82
83#[cfg(test)]
84mod tests {
85    use super::{em_to_px, px_to_em, px_to_rem, rem_to_px, FontSize, FontSizeError};
86
87    #[test]
88    fn converts_between_px_rem_and_em() {
89        let font = FontSize::new(18.0).unwrap();
90
91        assert_eq!(font.px(), 18.0);
92        assert!((font.rem(16.0).unwrap() - 1.125).abs() < 1.0e-12);
93        assert!((font.em(12.0).unwrap() - 1.5).abs() < 1.0e-12);
94        assert!((px_to_rem(18.0, 16.0).unwrap() - 1.125).abs() < 1.0e-12);
95        assert!((rem_to_px(1.125, 16.0).unwrap() - 18.0).abs() < 1.0e-12);
96        assert!((px_to_em(18.0, 12.0).unwrap() - 1.5).abs() < 1.0e-12);
97        assert!((em_to_px(1.5, 12.0).unwrap() - 18.0).abs() < 1.0e-12);
98    }
99
100    #[test]
101    fn rejects_invalid_font_size_inputs() {
102        assert_eq!(FontSize::new(0.0), Err(FontSizeError::InvalidFontSize));
103        assert_eq!(px_to_rem(-1.0, 16.0), Err(FontSizeError::InvalidFontSize));
104        assert_eq!(px_to_em(18.0, 0.0), Err(FontSizeError::InvalidParentSize));
105        assert_eq!(rem_to_px(f64::NAN, 16.0), Err(FontSizeError::InvalidRem));
106        assert_eq!(
107            em_to_px(1.0, f64::INFINITY),
108            Err(FontSizeError::InvalidParentSize)
109        );
110    }
111}