use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Emu(pub i64);
impl Emu {
pub const PER_INCH: i64 = 914_400;
pub const PER_CM: i64 = 360_000;
pub const PER_PT: i64 = 12_700;
pub const PER_PIXEL_96DPI: i64 = 9_525;
pub fn from_inches(inches: f64) -> Self {
Self((inches * Self::PER_INCH as f64) as i64)
}
pub fn from_cm(cm: f64) -> Self {
Self((cm * Self::PER_CM as f64) as i64)
}
pub fn from_pt(pt: f64) -> Self {
Self((pt * Self::PER_PT as f64) as i64)
}
pub fn to_inches(self) -> f64 {
self.0 as f64 / Self::PER_INCH as f64
}
pub fn to_cm(self) -> f64 {
self.0 as f64 / Self::PER_CM as f64
}
pub fn to_pt(self) -> f64 {
self.0 as f64 / Self::PER_PT as f64
}
pub fn to_twip(self) -> Twip {
Twip((self.0 / 635) as i32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Twip(pub i32);
impl Twip {
pub const PER_INCH: i32 = 1_440;
pub const PER_PT: i32 = 20;
pub fn from_pt(pt: f64) -> Self {
Self((pt * Self::PER_PT as f64) as i32)
}
pub fn from_inches(inches: f64) -> Self {
Self((inches * Self::PER_INCH as f64) as i32)
}
pub fn to_emu(self) -> Emu {
Emu(self.0 as i64 * 635)
}
pub fn to_inches(self) -> f64 {
self.0 as f64 / Self::PER_INCH as f64
}
pub fn to_pt(self) -> f64 {
self.0 as f64 / Self::PER_PT as f64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct HalfPoint(pub u32);
impl HalfPoint {
pub fn to_points(self) -> f64 {
self.0 as f64 / 2.0
}
pub fn from_points(pt: f64) -> Self {
Self((pt * 2.0) as u32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Percentage1000(pub i32);
impl Percentage1000 {
pub fn to_fraction(self) -> f64 {
self.0 as f64 / 100_000.0
}
pub fn to_percent(self) -> f64 {
self.0 as f64 / 1_000.0
}
pub fn from_percent(pct: f64) -> Self {
Self((pct * 1_000.0) as i32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Angle60k(pub i32);
impl Angle60k {
pub fn to_degrees(self) -> f64 {
self.0 as f64 / 60_000.0
}
pub fn from_degrees(deg: f64) -> Self {
Self((deg * 60_000.0) as i32)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emu_conversions() {
let one_inch = Emu::from_inches(1.0);
assert_eq!(one_inch.0, 914_400);
assert!((one_inch.to_inches() - 1.0).abs() < f64::EPSILON);
let one_cm = Emu::from_cm(1.0);
assert_eq!(one_cm.0, 360_000);
let one_pt = Emu::from_pt(1.0);
assert_eq!(one_pt.0, 12_700);
}
#[test]
fn emu_to_twip_roundtrip() {
let emu = Emu::from_inches(1.0);
let twip = emu.to_twip();
assert_eq!(twip.0, 1440);
let back = twip.to_emu();
assert!((back.0 - emu.0).abs() < 635);
}
#[test]
fn twip_conversions() {
let us_letter_width = Twip(12240); assert!((us_letter_width.to_inches() - 8.5).abs() < f64::EPSILON);
let twelve_pt = Twip::from_pt(12.0);
assert_eq!(twelve_pt.0, 240);
}
#[test]
fn half_point_conversions() {
let twelve_pt = HalfPoint(24);
assert!((twelve_pt.to_points() - 12.0).abs() < f64::EPSILON);
let from = HalfPoint::from_points(10.0);
assert_eq!(from.0, 20);
}
#[test]
fn percentage_conversions() {
let fifty = Percentage1000(50_000);
assert!((fifty.to_percent() - 50.0).abs() < f64::EPSILON);
assert!((fifty.to_fraction() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn angle_conversions() {
let right_angle = Angle60k(5_400_000);
assert!((right_angle.to_degrees() - 90.0).abs() < f64::EPSILON);
let from = Angle60k::from_degrees(45.0);
assert_eq!(from.0, 2_700_000);
}
}