latexml_core/common/
mudimension.rs1use std::fmt;
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use super::dimension::fixedformat;
7use crate::{
8 Object,
9 common::numeric_ops::{NumericOps, UNITY_F64, fixpoint, kround},
10 definition::register::RegisterType,
11};
12
13static MUDIM_SPEC_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(-?\d*\.?\d*)mu$").unwrap());
14
15#[derive(Debug, Copy, Clone, PartialEq, Default, Eq)]
16pub struct MuDimension(pub i64);
17
18impl NumericOps for MuDimension {
19 fn new(number: i64) -> Self { MuDimension(number) }
20 fn new_f64(number: f64) -> Self { MuDimension(kround(number)) }
21 fn value_of(self) -> i64 { self.0 }
22 fn register_type(&self) -> RegisterType { RegisterType::MuDimension }
23 fn unit(&self) -> Option<&'static str> { Some("mu") }
24 fn to_attribute(&self) -> String {
29 let fs = crate::state::lookup_font()
30 .and_then(|f| f.get_size())
31 .unwrap_or(10.0);
32 let muwidth = (fs * UNITY_F64 / 18.0) as i64;
33 let pt_scaled = ((self.0 as f64 * muwidth as f64 / UNITY_F64).trunc()) as i64;
34 super::dimension::attribute_format(pt_scaled, Some("pt"))
35 }
36}
37
38impl fmt::Display for MuDimension {
39 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40 write!(f, "{}", fixedformat(self.0, self.unit()))
41 }
42}
43impl Object for MuDimension {}
44
45impl MuDimension {
46 pub fn new_spec(spec: &str) -> Self {
47 if let Some(cap) = MUDIM_SPEC_RE.captures(spec) {
48 let num: f64 = cap
52 .get(1)
53 .map_or("", |m| m.as_str())
54 .parse::<f64>()
55 .unwrap_or(0.0);
56 MuDimension(fixpoint(num, Some(UNITY_F64)))
57 } else {
58 MuDimension(kround(spec.parse::<f64>().unwrap_or(0.0)))
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn mudim_default_is_zero() {
70 assert_eq!(MuDimension::default().value_of(), 0);
71 }
72
73 #[test]
74 fn mudim_new_builds_value() {
75 assert_eq!(MuDimension::new(65536).value_of(), 65536);
76 assert_eq!(MuDimension::new(0).value_of(), 0);
77 assert_eq!(MuDimension::new(-100).value_of(), -100);
78 }
79
80 #[test]
81 fn mudim_new_f64_rounds_knuthian() {
82 assert_eq!(MuDimension::new_f64(0.0).value_of(), 0);
83 assert_eq!(MuDimension::new_f64(1.0).value_of(), 1);
84 assert_eq!(MuDimension::new_f64(-1.0).value_of(), -1);
85 }
86
87 #[test]
88 fn mudim_register_type_is_mudimension() {
89 assert_eq!(
90 MuDimension::default().register_type(),
91 RegisterType::MuDimension
92 );
93 }
94
95 #[test]
96 fn mudim_unit_is_mu() {
97 assert_eq!(MuDimension::default().unit(), Some("mu"));
98 }
99
100 #[test]
101 fn mudim_display_includes_mu_unit() {
102 let m = MuDimension::new(65536); let out = format!("{m}");
104 assert!(out.ends_with("mu"), "got {out:?}");
105 }
106
107 #[test]
108 fn new_spec_parses_numeric_with_mu() {
109 let m = MuDimension::new_spec("1mu");
111 assert_ne!(m.value_of(), 0, "1mu should not be zero");
112 let m0 = MuDimension::new_spec("0mu");
114 assert_eq!(m0.value_of(), 0);
115 }
116
117 #[test]
118 fn new_spec_empty_numeric_part_is_zero() {
119 let m = MuDimension::new_spec("mu");
121 assert_eq!(m.value_of(), 0);
122 }
123
124 #[test]
125 fn new_spec_bad_input_is_zero() {
126 let m = MuDimension::new_spec("not a number");
129 assert_eq!(m.value_of(), 0);
130 }
131
132 #[test]
133 fn mudim_equality() {
134 assert_eq!(MuDimension::new(100), MuDimension::new(100));
135 assert_ne!(MuDimension::new(100), MuDimension::new(101));
136 }
137}