1#![forbid(unsafe_code)]
2#[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}