1use std::fmt;
2
3use super::glue::{FillCode, glue_string, new_setup, spec_setup};
4use crate::{
5 Object,
6 common::{dimension::attribute_format, numeric_ops::NumericOps},
7 definition::register::RegisterType,
8 token::{Catcode, Token},
9 tokens::Tokens,
10};
11
12#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
13pub struct MuGlue {
14 pub skip: i64,
15 pub plus: Option<i64>,
16 pub pfill: Option<FillCode>,
17 pub minus: Option<i64>,
18 pub mfill: Option<FillCode>,
19}
20
21impl fmt::Display for MuGlue {
22 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23 let string = glue_string(
24 self.skip, self.plus, self.pfill, self.minus, self.mfill, "mu",
25 );
26 write!(f, "{string}")
27 }
28}
29
30impl NumericOps for MuGlue {
31 fn value_of(self) -> i64 { self.skip }
32 fn register_type(&self) -> RegisterType { RegisterType::MuGlue }
33 fn new(skip: i64) -> Self {
34 MuGlue {
35 skip,
36 plus: None,
37 pfill: None,
38 minus: None,
39 mfill: None,
40 }
41 }
42 fn new_f64(number: f64) -> Self {
43 let (skip, plus, pfill, minus, mfill) = new_setup(number, None, None, None, None);
44 MuGlue {
45 skip,
46 plus,
47 pfill,
48 minus,
49 mfill,
50 }
51 }
52 fn negate(self) -> Self {
54 MuGlue {
55 skip: -self.skip,
56 plus: self.plus.map(|p| -p),
57 pfill: self.pfill,
58 minus: self.minus.map(|m| -m),
59 mfill: self.mfill,
60 }
61 }
62}
63impl Object for MuGlue {
64 fn revert(&self) -> crate::Result<Tokens> { Ok(Tokens::new(Explode!(self.to_string()))) }
65}
66
67impl MuGlue {
68 pub fn new_full(
69 skip: i64,
70 plus: Option<i64>,
71 pfill: Option<FillCode>,
72 minus: Option<i64>,
73 mfill: Option<FillCode>,
74 ) -> Self {
75 MuGlue {
76 skip,
77 plus,
78 pfill,
79 minus,
80 mfill,
81 }
82 }
83 pub fn new_full_f64(
84 skip: f64,
85 plus: Option<f64>,
86 pfill: Option<FillCode>,
87 minus: Option<f64>,
88 mfill: Option<FillCode>,
89 ) -> Self {
90 let (skip, plus, pfill, minus, mfill) = new_setup(skip, plus, pfill, minus, mfill);
91 MuGlue {
92 skip,
93 plus,
94 pfill,
95 minus,
96 mfill,
97 }
98 }
99
100 pub fn new_spec(
101 spec: &str,
102 plus: Option<f64>,
103 pfill: Option<FillCode>,
104 minus: Option<f64>,
105 mfill: Option<FillCode>,
106 ) -> Self {
107 let (skip, plus, pfill, minus, mfill) = spec_setup(spec, plus, pfill, minus, mfill, "mu");
108 MuGlue {
109 skip,
110 plus,
111 pfill,
112 minus,
113 mfill,
114 }
115 }
116
117 pub fn to_attribute(&self) -> String {
118 let pt_skip = mu_to_pt(self.skip);
124 let mut string = attribute_format(pt_skip, Some("pt"));
125 if let Some(plus) = self.plus
126 && plus != 0
127 {
128 string.push_str(" plus ");
129 let fill_u = if let Some(pfill) = self.pfill {
130 pfill.to_str()
131 } else {
132 "pt"
133 };
134 let plus_pt = if fill_u == "pt" { mu_to_pt(plus) } else { plus };
135 string.push_str(&attribute_format(plus_pt, Some(fill_u)));
136 }
137 if let Some(minus) = self.minus
138 && minus != 0
139 {
140 string.push_str(" minus ");
141 let mfill_u = if let Some(mfill) = self.mfill {
142 mfill.to_str()
143 } else {
144 "pt"
145 };
146 let minus_pt = if mfill_u == "pt" {
147 mu_to_pt(minus)
148 } else {
149 minus
150 };
151 string.push_str(&attribute_format(minus_pt, Some(mfill_u)));
152 }
153 string
154 }
155}
156
157fn mu_to_pt(mu_val: i64) -> i64 {
158 let fs = crate::state::lookup_font()
159 .and_then(|f| f.get_size())
160 .unwrap_or(10.0);
161 let unity = crate::common::numeric_ops::UNITY_F64;
162 let muwidth = (fs * unity / 18.0) as i64;
163 ((mu_val as f64 * muwidth as f64 / unity).trunc()) as i64
164}
165
166impl From<MuGlue> for Option<Tokens> {
167 fn from(v: MuGlue) -> Option<Tokens> { Some(v.into()) }
168}
169impl From<MuGlue> for Tokens {
170 fn from(v: MuGlue) -> Tokens {
171 v.revert()
172 .expect("MuGlue should always be revertable to Tokens.")
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn muglue_default_is_zero() {
182 let m = MuGlue::default();
183 assert_eq!(m.skip, 0);
184 assert_eq!(m.plus, None);
185 assert_eq!(m.minus, None);
186 assert_eq!(m.pfill, None);
187 assert_eq!(m.mfill, None);
188 }
189
190 #[test]
191 fn muglue_new_builds_skip_only() {
192 let m = <MuGlue as NumericOps>::new(65536);
193 assert_eq!(m.skip, 65536);
194 assert_eq!(m.plus, None);
195 assert_eq!(m.minus, None);
196 }
197
198 #[test]
199 fn muglue_value_of_returns_skip() {
200 let m = MuGlue {
201 skip: 1234,
202 plus: Some(10),
203 pfill: None,
204 minus: None,
205 mfill: None,
206 };
207 assert_eq!(m.value_of(), 1234);
208 }
209
210 #[test]
211 fn muglue_register_type_is_muglue() {
212 let m = MuGlue::default();
213 assert_eq!(m.register_type(), RegisterType::MuGlue);
214 }
215
216 #[test]
217 fn muglue_negate_flips_all_components() {
218 let m = MuGlue {
219 skip: 100,
220 plus: Some(10),
221 pfill: Some(FillCode::Fil),
222 minus: Some(5),
223 mfill: Some(FillCode::Fill),
224 };
225 let n = m.negate();
226 assert_eq!(n.skip, -100);
227 assert_eq!(n.plus, Some(-10));
228 assert_eq!(n.minus, Some(-5));
229 assert_eq!(n.pfill, Some(FillCode::Fil));
231 assert_eq!(n.mfill, Some(FillCode::Fill));
232 }
233
234 #[test]
235 fn muglue_new_full_roundtrip() {
236 let m = MuGlue::new_full(
237 100,
238 Some(10),
239 Some(FillCode::Fil),
240 Some(5),
241 Some(FillCode::Fill),
242 );
243 assert_eq!(m.skip, 100);
244 assert_eq!(m.plus, Some(10));
245 assert_eq!(m.pfill, Some(FillCode::Fil));
246 assert_eq!(m.minus, Some(5));
247 assert_eq!(m.mfill, Some(FillCode::Fill));
248 }
249
250 #[test]
251 fn muglue_display_includes_mu_unit() {
252 let m = <MuGlue as NumericOps>::new(65536); let out = format!("{m}");
255 assert!(
256 out.contains("mu"),
257 "MuGlue display should include 'mu' unit; got {out:?}"
258 );
259 }
260
261 #[test]
262 fn muglue_equality() {
263 let a = MuGlue {
264 skip: 1,
265 plus: None,
266 pfill: None,
267 minus: None,
268 mfill: None,
269 };
270 let b = MuGlue {
271 skip: 1,
272 plus: None,
273 pfill: None,
274 minus: None,
275 mfill: None,
276 };
277 let c = MuGlue {
278 skip: 2,
279 plus: None,
280 pfill: None,
281 minus: None,
282 mfill: None,
283 };
284 assert_eq!(a, b);
285 assert_ne!(a, c);
286 }
287
288 #[test]
289 fn muglue_double_negate_is_identity() {
290 let m = MuGlue {
291 skip: 100,
292 plus: Some(10),
293 pfill: Some(FillCode::Fil),
294 minus: Some(5),
295 mfill: Some(FillCode::Fill),
296 };
297 assert_eq!(m.negate().negate(), m);
298 }
299}