Skip to main content

use_type_unit/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive typographic unit helpers.
3//!
4//! These helpers convert between common typographic units using explicit root
5//! and parent font sizes where required.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_type_unit::{TypeUnit, em_to_px, pt_to_px, px_to_pt, rem_to_px};
11//!
12//! assert!((pt_to_px(12.0).unwrap() - 15.999_996).abs() < 1.0e-12);
13//! assert!((px_to_pt(16.0).unwrap() - 12.000_003_000_000_75).abs() < 1.0e-12);
14//! assert!((rem_to_px(1.25, 16.0).unwrap() - 20.0).abs() < 1.0e-12);
15//! assert!((em_to_px(1.5, 18.0).unwrap() - 27.0).abs() < 1.0e-12);
16//! assert!((TypeUnit::Pt(12.0).to_px(16.0, 18.0).unwrap() - 15.999_996).abs() < 1.0e-12);
17//! ```
18
19const 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}