1#![forbid(unsafe_code)]
2const PT_IN_PX: f64 = 1.333_333;
20
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum TypeUnit {
23 Px(f64),
24 Rem(f64),
25 Em(f64),
26 Pt(f64),
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum TypeUnitError {
31 InvalidPx,
32 InvalidRem,
33 InvalidEm,
34 InvalidPt,
35 InvalidRootSize,
36 InvalidParentSize,
37}
38
39fn validate_non_negative(value: f64, error: TypeUnitError) -> Result<f64, TypeUnitError> {
40 if !value.is_finite() || value < 0.0 {
41 Err(error)
42 } else {
43 Ok(value)
44 }
45}
46
47fn validate_positive_reference(value: f64, error: TypeUnitError) -> Result<f64, TypeUnitError> {
48 if !value.is_finite() || value <= 0.0 {
49 Err(error)
50 } else {
51 Ok(value)
52 }
53}
54
55impl TypeUnit {
56 pub fn to_px(&self, root_px: f64, parent_px: f64) -> Result<f64, TypeUnitError> {
57 match *self {
58 Self::Px(px) => validate_non_negative(px, TypeUnitError::InvalidPx),
59 Self::Rem(rem) => rem_to_px(rem, root_px),
60 Self::Em(em) => em_to_px(em, parent_px),
61 Self::Pt(pt) => pt_to_px(pt),
62 }
63 }
64}
65
66pub fn pt_to_px(pt: f64) -> Result<f64, TypeUnitError> {
67 Ok(validate_non_negative(pt, TypeUnitError::InvalidPt)? * PT_IN_PX)
68}
69
70pub fn px_to_pt(px: f64) -> Result<f64, TypeUnitError> {
71 Ok(validate_non_negative(px, TypeUnitError::InvalidPx)? / PT_IN_PX)
72}
73
74pub fn rem_to_px(rem: f64, root_px: f64) -> Result<f64, TypeUnitError> {
75 Ok(validate_non_negative(rem, TypeUnitError::InvalidRem)?
76 * validate_positive_reference(root_px, TypeUnitError::InvalidRootSize)?)
77}
78
79pub fn em_to_px(em: f64, parent_px: f64) -> Result<f64, TypeUnitError> {
80 Ok(validate_non_negative(em, TypeUnitError::InvalidEm)?
81 * validate_positive_reference(parent_px, TypeUnitError::InvalidParentSize)?)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::{TypeUnit, TypeUnitError, em_to_px, pt_to_px, px_to_pt, rem_to_px};
87
88 #[test]
89 fn converts_between_typographic_units() {
90 assert!((pt_to_px(12.0).unwrap() - 15.999_996).abs() < 1.0e-12);
91 assert!((px_to_pt(16.0).unwrap() - 12.000_003_000_000_75).abs() < 1.0e-12);
92 assert!((rem_to_px(1.25, 16.0).unwrap() - 20.0).abs() < 1.0e-12);
93 assert!((em_to_px(1.5, 18.0).unwrap() - 27.0).abs() < 1.0e-12);
94 assert!((TypeUnit::Rem(1.25).to_px(16.0, 18.0).unwrap() - 20.0).abs() < 1.0e-12);
95 assert!((TypeUnit::Pt(12.0).to_px(16.0, 18.0).unwrap() - 15.999_996).abs() < 1.0e-12);
96 assert_eq!(TypeUnit::Px(0.0).to_px(16.0, 18.0).unwrap(), 0.0);
97 }
98
99 #[test]
100 fn rejects_invalid_unit_inputs() {
101 assert_eq!(pt_to_px(-1.0), Err(TypeUnitError::InvalidPt));
102 assert_eq!(px_to_pt(f64::NAN), Err(TypeUnitError::InvalidPx));
103 assert_eq!(rem_to_px(1.0, 0.0), Err(TypeUnitError::InvalidRootSize));
104 assert_eq!(
105 em_to_px(1.0, f64::NEG_INFINITY),
106 Err(TypeUnitError::InvalidParentSize)
107 );
108 assert_eq!(
109 TypeUnit::Em(-0.5).to_px(16.0, 18.0),
110 Err(TypeUnitError::InvalidEm)
111 );
112 }
113}