1use std::{cmp::Ordering, fmt};
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use super::dimension::fixedformat;
7use crate::{
8 Object,
9 common::{
10 dimension::attribute_format,
11 error::Result,
12 numeric_ops::{EPSILON, NumericOps, fixpoint, fixpoint_unit, kround},
13 },
14 definition::register::{RegisterType, RegisterValue},
15 digested::Digested,
16 state::*,
17};
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
23pub enum FillCode {
24 Fil,
25 Fill,
26 Filll,
27}
28
29impl fmt::Display for FillCode {
30 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.to_str()) }
31}
32
33impl FillCode {
34 pub fn new(index: usize) -> Option<FillCode> {
35 match index {
36 1 => Some(FillCode::Fil),
37 2 => Some(FillCode::Fill),
38 3 => Some(FillCode::Filll),
39 _ => None,
40 }
41 }
42 pub fn from(ftype: &str) -> Option<FillCode> {
43 match ftype {
44 "fil" => Some(FillCode::Fil),
45 "fill" => Some(FillCode::Fill),
46 "filll" => Some(FillCode::Filll),
47 _ => None,
48 }
49 }
50 pub fn to_str(&self) -> &'static str {
51 match self {
52 FillCode::Fil => "fil",
53 FillCode::Fill => "fill",
54 FillCode::Filll => "filll",
55 }
56 }
57}
58
59macro_rules! num_re_str {
62 () => {
63 r"\d*\.?\d*"
64 };
65}
66macro_rules! unit_re_str {
67 () => {
68 r"[a-zA-Z][a-zA-Z]"
69 };
70}
71macro_rules! fill_re_str {
72 () => {
73 r"fil|fill|filll|[a-zA-Z][a-zA-Z]"
74 };
75}
76
77macro_rules! plus_re_str {
78 () => {
79 concat!(r"\s+plus\s*(", num_re_str!(), ")(", fill_re_str!(), r")")
80 };
81}
82macro_rules! minus_re_str {
83 () => {
84 concat!(r"\s+minus\s*(", num_re_str!(), r")(", fill_re_str!(), r")")
85 };
86}
87
88static GLUE_RE_STR: &str = concat!(
89 r"^(\+?\-?",
90 num_re_str!(),
91 r")(",
92 unit_re_str!(),
93 r")(",
94 plus_re_str!(),
95 r")?(",
96 minus_re_str!(),
97 r")?$"
98);
99
100static _NUM_RE: Lazy<Regex> = Lazy::new(|| Regex::new(num_re_str!()).unwrap());
101static UNIT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(unit_re_str!()).unwrap());
102static _FILL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(fill_re_str!()).unwrap());
103static _PLUS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(plus_re_str!()).unwrap());
104static _MINUS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(minus_re_str!()).unwrap());
105static GLUE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(GLUE_RE_STR).unwrap());
106
107#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
108pub struct Glue {
109 pub skip: i64,
110 pub plus: Option<i64>,
111 pub pfill: Option<FillCode>,
112 pub minus: Option<i64>,
113 pub mfill: Option<FillCode>,
114}
115
116impl NumericOps for Glue {
117 fn value_of(self) -> i64 { self.skip }
118 fn register_type(&self) -> RegisterType { RegisterType::Glue }
119 fn into_glue_type(self) -> Glue { self }
121 fn add<T: NumericOps>(self, other: T) -> Self
122 where Self: Sized {
123 if other.register_type() != RegisterType::Glue {
124 Glue {
125 skip: self.skip + other.value_of(),
126 plus: self.plus,
127 pfill: self.pfill,
128 minus: self.minus,
129 mfill: self.mfill,
130 }
131 } else {
132 self.add_glue(other.into_glue_type())
134 }
135 }
136 fn new(skip: i64) -> Self {
137 Glue {
138 skip,
139 plus: None,
140 pfill: None,
141 minus: None,
142 mfill: None,
143 }
144 }
145 fn new_f64(number: f64) -> Self {
146 let (skip, plus, pfill, minus, mfill) = new_setup(number, None, None, None, None);
147 Glue {
148 skip,
149 plus,
150 pfill,
151 minus,
152 mfill,
153 }
154 }
155 fn multiply<T: NumericOps>(self, other: T) -> Self
157 where Self: Sized {
158 let factor = other.value_f64();
159 Glue {
160 skip: (self.skip as f64 * factor) as i64,
161 plus: self.plus.map(|p| (p as f64 * factor) as i64),
162 pfill: self.pfill,
163 minus: self.minus.map(|m| (m as f64 * factor) as i64),
164 mfill: self.mfill,
165 }
166 }
167 fn divide<T: NumericOps>(self, other: T) -> Self
169 where Self: Sized {
170 let mut divisor = other.value_f64();
171 if divisor == 0.0 {
172 divisor = EPSILON;
173 }
174 Glue {
175 skip: (self.skip as f64 / divisor).trunc() as i64,
176 plus: self.plus.map(|p| (p as f64 / divisor).trunc() as i64),
177 pfill: self.pfill,
178 minus: self.minus.map(|m| (m as f64 / divisor).trunc() as i64),
179 mfill: self.mfill,
180 }
181 }
182 fn subtract<T: NumericOps>(self, other: T) -> Self
183 where Self: Sized {
184 if other.register_type() != RegisterType::Glue {
185 Glue {
186 skip: self.skip - other.value_of(),
187 plus: self.plus,
188 pfill: self.pfill,
189 minus: self.minus,
190 mfill: self.mfill,
191 }
192 } else {
193 let other_glue = other.into_glue_type();
194 self.add_glue(Glue {
195 skip: -other_glue.skip,
196 plus: other_glue.plus.map(|p| -p),
197 pfill: other_glue.pfill,
198 minus: other_glue.minus.map(|m| -m),
199 mfill: other_glue.mfill,
200 })
201 }
202 }
203 fn negate(self) -> Self {
205 Glue {
206 skip: -self.skip,
207 plus: self.plus.map(|p| -p),
208 pfill: self.pfill,
209 minus: self.minus.map(|m| -m),
210 mfill: self.mfill,
211 }
212 }
213 fn smaller<T: NumericOps>(self, other: T) -> Self
214 where Self: Sized {
215 let other_val = other.value_of();
216 if self.skip <= other_val {
217 self
218 } else {
219 Self::new(other_val)
220 }
221 }
222 fn larger<T: NumericOps>(self, other: T) -> Self
223 where Self: Sized {
224 let other_val = other.value_of();
225 if self.skip >= other_val {
226 self
227 } else {
228 Self::new(other_val)
229 }
230 }
231}
232
233pub fn glue_string(
234 skip: i64,
235 plus_opt: Option<i64>,
236 pfill_opt: Option<FillCode>,
237 minus_opt: Option<i64>,
238 mfill_opt: Option<FillCode>,
239 unit: &str,
240) -> String {
241 let mut string = fixedformat(skip, Some(unit));
244 if let Some(plus) = plus_opt
245 && plus != 0
246 {
247 string.push_str(" plus ");
248 let p_fill = if let Some(fill) = pfill_opt {
249 fill.to_str()
250 } else {
251 unit
252 };
253 string.push_str(&fixedformat(plus, Some(p_fill)))
254 }
255 if let Some(minus) = minus_opt
256 && minus != 0
257 {
258 string.push_str(" minus ");
259 let p_fill = if let Some(fill) = mfill_opt {
260 fill.to_str()
261 } else {
262 unit
263 };
264 string.push_str(&fixedformat(minus, Some(p_fill)))
265 }
266 string
267}
268
269impl fmt::Display for Glue {
270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 let string = glue_string(
272 self.skip, self.plus, self.pfill, self.minus, self.mfill, "pt",
273 );
274 write!(f, "{string}")
275 }
276}
277impl Object for Glue {
278 fn be_digested(self) -> Result<Digested> { Ok(RegisterValue::Glue(self).into()) }
279}
280
281pub fn new_setup(
282 skip: f64,
283 plus: Option<f64>,
284 pfill: Option<FillCode>,
285 minus: Option<f64>,
286 mfill: Option<FillCode>,
287) -> (
288 i64,
289 Option<i64>,
290 Option<FillCode>,
291 Option<i64>,
292 Option<FillCode>,
293) {
294 (
296 kround(skip),
297 plus.map(kround),
298 pfill,
299 minus.map(kround),
300 mfill,
301 )
302}
303
304pub fn spec_setup(
305 spec: &str,
306 plus: Option<f64>,
307 mut pfill: Option<FillCode>,
308 minus: Option<f64>,
309 mut mfill: Option<FillCode>,
310 unit: &str,
311) -> (
312 i64,
313 Option<i64>,
314 Option<FillCode>,
315 Option<i64>,
316 Option<FillCode>,
317) {
318 if !UNIT_RE.is_match(spec) {
319 let skip: f64 = spec.parse::<f64>().unwrap_or_default();
321 new_setup(skip, plus, pfill, minus, mfill)
322 } else {
323 let is_mu = unit == "mu";
324 if plus.is_some() || pfill.is_some() || minus.is_some() || mfill.is_some() {
325 let msg = s!(
326 "You should not create {} with both units and stretch",
327 if is_mu { "MuGlue" } else { "Glue" }
328 );
329 Warn!("unexpected", "fill", msg);
330 }
331
332 if let Some(cs) = GLUE_RE.captures(spec) {
333 let (f, unit, p, punit, m, munit) = (
334 cs.get(1)
335 .map(|v| v.as_str().parse::<f64>().unwrap_or_default())
336 .unwrap_or_default(),
337 cs.get(2).map_or("", |m| m.as_str()),
338 cs.get(4)
339 .map(|v| v.as_str().parse::<f64>().unwrap_or_default())
340 .unwrap_or_default(),
341 cs.get(5).map_or("", |m| m.as_str()),
342 cs.get(7)
343 .map(|v| v.as_str().parse::<f64>().unwrap_or_default())
344 .unwrap_or_default(),
345 cs.get(8).map_or("", |m| m.as_str()),
346 );
347 let skip = if unit.is_empty() {
348 f.trunc() as i64
349 } else if is_mu {
350 if unit != "mu" {
351 Warn!("unexpected", unit, "Assumed mu");
352 }
353 fixpoint(f, None) } else {
355 {
356 let (num, den) = convert_unit_ratio(unit);
357 fixpoint_unit(f, num, den)
358 }
359 };
360
361 let mut plus = if punit.is_empty() {
362 None } else if let Some(code) = FillCode::from(punit) {
365 pfill = Some(code);
366 Some(fixpoint(p, None))
367 } else if is_mu {
368 pfill = None;
369 if punit != "mu" {
370 Warn!("unexpected", punit, "Assumed mu");
371 }
372 Some(fixpoint(p, None))
373 } else {
374 pfill = None; Some({
376 let (num, den) = convert_unit_ratio(punit);
377 fixpoint_unit(p, num, den)
378 })
379 };
380
381 let mut minus = if munit.is_empty() {
382 None } else if let Some(code) = FillCode::from(munit) {
385 mfill = Some(code);
386 Some(fixpoint(m, None))
387 } else if is_mu {
388 mfill = None; if munit != "mu" {
390 Warn!("unexpected", munit, "Assumed mu");
391 }
392 Some(fixpoint(m, None))
393 } else {
394 mfill = None; Some({
396 let (num, den) = convert_unit_ratio(munit);
397 fixpoint_unit(m, num, den)
398 })
399 };
400
401 if punit.is_empty() {
402 } else if let Some(pfcode) = FillCode::from(punit) {
403 plus = Some(fixpoint(p, None));
404 pfill = Some(pfcode);
405 } else {
406 plus = Some({
407 let (num, den) = convert_unit_ratio(punit);
408 fixpoint_unit(p, num, den)
409 });
410 pfill = None;
411 }
412 if munit.is_empty() {
413 } else if let Some(mfcode) = FillCode::from(munit) {
414 minus = Some(fixpoint(m, None));
415 mfill = Some(mfcode);
416 } else {
417 minus = Some({
418 let (num, den) = convert_unit_ratio(munit);
419 fixpoint_unit(m, num, den)
420 });
421 mfill = None;
422 }
423 (skip, plus, pfill, minus, mfill)
424 } else {
425 let msg = s!(
426 "Missing {} specification assuming 0pt",
427 if is_mu { "MuGlue" } else { "Glue" }
428 );
429 Warn!("unexpected", spec, msg);
430 (0, None, None, None, None)
431 }
432 }
433}
434
435impl Glue {
436 pub fn new_full(
437 skip: i64,
438 plus: Option<i64>,
439 pfill: Option<FillCode>,
440 minus: Option<i64>,
441 mfill: Option<FillCode>,
442 ) -> Self {
443 Glue {
444 skip,
445 plus,
446 pfill,
447 minus,
448 mfill,
449 }
450 }
451 pub fn new_full_f64(
452 skip: f64,
453 plus: Option<f64>,
454 pfill: Option<FillCode>,
455 minus: Option<f64>,
456 mfill: Option<FillCode>,
457 ) -> Self {
458 let (skip, plus, pfill, minus, mfill) = new_setup(skip, plus, pfill, minus, mfill);
459 Glue {
460 skip,
461 plus,
462 pfill,
463 minus,
464 mfill,
465 }
466 }
467 pub fn new_spec(
468 spec: &str,
469 plus: Option<f64>,
470 pfill: Option<FillCode>,
471 minus: Option<f64>,
472 mfill: Option<FillCode>,
473 ) -> Self {
474 let (skip, plus, pfill, minus, mfill) = spec_setup(spec, plus, pfill, minus, mfill, "pt");
475 Glue {
476 skip,
477 plus,
478 pfill,
479 minus,
480 mfill,
481 }
482 }
483
484 pub fn add_glue(self, other: Glue) -> Glue {
485 let skip = self.skip + other.skip;
489 let mut plus = self.plus;
490 let mut minus = self.minus;
491 let mut pfill = self.pfill;
492 let mut mfill = self.mfill;
493
494 match self.pfill.cmp(&other.pfill) {
495 Ordering::Equal => {
496 if let Some(oplus) = other.plus {
497 plus = match plus {
498 Some(splus) => Some(splus + oplus),
499 None => Some(oplus),
500 };
501 }
502 },
503 Ordering::Less => {
504 plus = other.plus;
505 pfill = other.pfill;
506 },
507 _ => {},
508 };
509 match self.mfill.cmp(&other.mfill) {
510 Ordering::Equal => {
511 if let Some(ominus) = other.minus {
512 minus = match minus {
513 Some(sminus) => Some(sminus + ominus),
514 None => Some(ominus),
515 };
516 }
517 },
518 Ordering::Less => {
519 minus = other.minus;
520 mfill = other.mfill;
521 },
522 _ => {},
523 };
524
525 Glue {
526 skip,
527 plus,
528 pfill,
529 minus,
530 mfill,
531 }
532 }
535
536 pub fn to_attribute(&self) -> String {
537 let u = "pt";
538 let mut string = attribute_format(self.skip, Some(u));
539 if let Some(plus) = self.plus
540 && plus != 0
541 {
542 string.push_str(" plus ");
543 let fill_u = if let Some(pfill) = self.pfill {
544 pfill.to_str()
545 } else {
546 u
547 };
548 string.push_str(&attribute_format(plus, Some(fill_u)));
549 }
550 if let Some(minus) = self.minus
551 && minus != 0
552 {
553 string.push_str(" minus ");
554 let mfill_u = if let Some(mfill) = self.mfill {
555 mfill.to_str()
556 } else {
557 u
558 };
559 string.push_str(&attribute_format(minus, Some(mfill_u)));
560 }
561 string
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568
569 #[test]
570 fn fillcode_new_from_index() {
571 assert_eq!(FillCode::new(1), Some(FillCode::Fil));
572 assert_eq!(FillCode::new(2), Some(FillCode::Fill));
573 assert_eq!(FillCode::new(3), Some(FillCode::Filll));
574 assert_eq!(FillCode::new(0), None);
575 assert_eq!(FillCode::new(4), None);
576 assert_eq!(FillCode::new(100), None);
577 }
578
579 #[test]
580 fn fillcode_from_str_case_sensitive() {
581 assert_eq!(FillCode::from("fil"), Some(FillCode::Fil));
582 assert_eq!(FillCode::from("fill"), Some(FillCode::Fill));
583 assert_eq!(FillCode::from("filll"), Some(FillCode::Filll));
584 assert_eq!(FillCode::from("FIL"), None);
586 assert_eq!(FillCode::from("Fil"), None);
587 assert_eq!(FillCode::from(""), None);
588 assert_eq!(FillCode::from("other"), None);
589 }
590
591 #[test]
592 fn fillcode_to_str_roundtrip() {
593 for code in [FillCode::Fil, FillCode::Fill, FillCode::Filll] {
595 let s = code.to_str();
596 assert_eq!(
597 FillCode::from(s),
598 Some(code),
599 "roundtrip broke at {code:?} via {s:?}"
600 );
601 }
602 }
603
604 #[test]
605 fn fillcode_display_matches_to_str() {
606 assert_eq!(format!("{}", FillCode::Fil), "fil");
607 assert_eq!(format!("{}", FillCode::Fill), "fill");
608 assert_eq!(format!("{}", FillCode::Filll), "filll");
609 }
610
611 #[test]
612 fn fillcode_ord_fil_lt_fill_lt_filll() {
613 assert!(FillCode::Fil < FillCode::Fill);
615 assert!(FillCode::Fill < FillCode::Filll);
616 }
617
618 #[test]
619 fn glue_default_is_zero_skip_no_stretch() {
620 let g = Glue::default();
621 assert_eq!(g.skip, 0);
622 assert_eq!(g.plus, None);
623 assert_eq!(g.minus, None);
624 assert_eq!(g.pfill, None);
625 assert_eq!(g.mfill, None);
626 }
627
628 #[test]
629 fn glue_new_builds_skip_only() {
630 let g = <Glue as NumericOps>::new(65536);
631 assert_eq!(g.skip, 65536);
632 assert_eq!(g.plus, None);
633 assert_eq!(g.minus, None);
634 }
635
636 #[test]
637 fn glue_value_of_returns_skip() {
638 let g = Glue {
639 skip: 1234,
640 plus: Some(10),
641 pfill: None,
642 minus: None,
643 mfill: None,
644 };
645 assert_eq!(
646 g.value_of(),
647 1234,
648 "value_of returns the skip, not the stretch"
649 );
650 }
651}