latexml_core/common/
dimension.rs1use std::fmt;
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use crate::{
7 Digested, RegisterValue,
8 common::{
9 error::*,
10 numeric_ops::{NumericOps, UNITY, UNITY_F64, fixpoint_unit, kround, round_to},
11 object::Object,
12 },
13 definition::register::RegisterType,
14 state::*,
15 tokens::Tokens,
16};
17
18static SPEC_RE: Lazy<Regex> =
19 Lazy::new(|| Regex::new(r"^(-?\d*\.?\d*)([a-zA-Z][a-zA-Z])$").unwrap());
20
21#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
22pub struct Dimension(pub i64);
23
24impl Object for Dimension {
25 fn revert(&self) -> Result<Tokens> { Ok(Tokens::new(ExplodeText!(&self.to_string()))) }
26 fn be_digested(self) -> Result<Digested>
27 where
28 Self: Sized,
29 Self: fmt::Debug,
30 {
31 Ok(Digested::from(RegisterValue::Dimension(self)))
32 }
33}
34impl NumericOps for Dimension {
35 fn new(number: i64) -> Self { Dimension(number) }
36 fn new_f64(number: f64) -> Self { Dimension(kround(number)) }
37 fn value_of(self) -> i64 { self.0 }
38 fn register_type(&self) -> RegisterType { RegisterType::Dimension }
39 fn unit(&self) -> Option<&'static str> { Some("pt") }
40 fn to_attribute(&self) -> String { attribute_format(self.value_of(), self.unit()) }
41}
42
43impl fmt::Display for Dimension {
44 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45 write!(f, "{}", fixedformat(self.0, self.unit()))
46 }
47}
48
49impl Dimension {
50 pub fn em_value(&self, prec: Option<u8>, font: Option<&crate::common::font::Font>) -> f64 {
52 let em_width: f64 = if let Some(f) = font {
53 f.get_em_width() as f64
54 } else {
55 lookup_font()
56 .map(|f| f.get_em_width() as f64)
57 .unwrap_or(UNITY_F64 * 10.0) };
59 round_to(self.0 as f64 / em_width, prec)
60 }
61
62 pub fn spec_to_f64(spec: &str) -> Result<f64> {
63 if spec.is_empty() {
64 Ok(0.0)
65 } else if let Some(cap) = SPEC_RE.captures(spec) {
66 let num_str = cap.get(1).map_or("", |m| m.as_str());
70 let num: f64 = num_str.parse::<f64>().unwrap_or(0.0);
71 let unit = cap.get(2).map_or("", |m| m.as_str());
72 let (conv_num, conv_den) = convert_unit_ratio(unit);
73 Ok(fixpoint_unit(num, conv_num, conv_den) as f64)
74 } else {
75 Ok(kround(spec.parse::<f64>().unwrap_or(0.0)) as f64)
84 }
85 }
86}
87
88impl std::str::FromStr for Dimension {
89 type Err = Error;
90 fn from_str(spec: &str) -> Result<Dimension> {
91 Ok(Dimension::new_f64(Dimension::spec_to_f64(spec)?))
92 }
93}
94
95pub fn fixedformat(mut s: i64, unit_opt: Option<&str>) -> String {
100 use std::fmt::Write as _;
101 let mut string = String::new();
105 if s < 0 {
106 string.push('-');
107 s = -s;
108 }
109 write!(string, "{}", s / UNITY).unwrap();
110 string.push('.');
111 s = 10 * (s % UNITY) + 5;
112 let mut delta = 10;
113 loop {
114 if delta > UNITY {
115 s += 0x8000 - 50000;
116 }
117 write!(string, "{}", s / UNITY).unwrap();
118 s = 10 * (s % UNITY);
119 delta *= 10;
120 if s <= delta {
121 break;
122 }
123 }
124 if let Some(unit) = unit_opt {
125 string.push_str(unit);
126 }
127 string
128}
129
130pub fn attribute_format(sp: i64, unit_opt: Option<&str>) -> String {
131 let unit = unit_opt.unwrap_or("pt");
132 s!("{:.1}{unit}", round_to(sp as f64 / UNITY_F64, Some(1)))
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn fixedformat_zero() {
141 assert_eq!(fixedformat(0, Some("pt")), "0.0pt");
143 assert_eq!(fixedformat(0, None), "0.0");
144 }
145
146 #[test]
147 fn fixedformat_one_sp_unit() {
148 assert_eq!(fixedformat(UNITY, Some("pt")), "1.0pt");
150 assert_eq!(fixedformat(2 * UNITY, Some("pt")), "2.0pt");
151 }
152
153 #[test]
154 fn fixedformat_negative() {
155 assert_eq!(fixedformat(-UNITY, Some("pt")), "-1.0pt");
156 assert_eq!(fixedformat(-2 * UNITY, Some("pt")), "-2.0pt");
157 }
158
159 #[test]
160 fn fixedformat_half_pt() {
161 let out = fixedformat(UNITY / 2, Some("pt"));
163 assert_eq!(out, "0.5pt");
164 }
165
166 #[test]
167 fn attribute_format_defaults_to_pt() {
168 assert_eq!(attribute_format(UNITY, None), "1.0pt");
169 assert_eq!(attribute_format(UNITY, Some("pt")), "1.0pt");
170 }
171
172 #[test]
173 fn attribute_format_other_unit() {
174 assert_eq!(attribute_format(UNITY, Some("in")), "1.0in");
175 }
176
177 #[test]
178 fn attribute_format_rounds_to_one_decimal() {
179 let sp = (UNITY_F64 * 1.25) as i64;
181 let out = attribute_format(sp, None);
182 assert_eq!(out, "1.3pt", "got {out:?}");
183 }
184
185 #[test]
186 fn spec_to_f64_empty_is_zero() {
187 assert_eq!(Dimension::spec_to_f64("").unwrap(), 0.0);
188 }
189
190 #[test]
191 fn spec_to_f64_bare_number_is_scaled() {
192 let out = Dimension::spec_to_f64("65536").unwrap();
195 assert_eq!(out, 65536.0);
196 }
197}