common/
lib.rs

1//! Core types and abstractions used in Texcraft.
2//!
3//!
4
5use std::fmt::Write;
6
7/// Trait satisfied by font formats (like .tfm files).
8pub trait FontFormat: Sized {
9    const DEFAULT_FILE_EXTENSION: &'static str;
10    type Error: std::error::Error + 'static;
11
12    /// Parse binary data into a font.
13    fn parse(b: &[u8]) -> Result<Self, Self::Error>;
14}
15
16/// Identifier for a font.
17///
18/// The zero value is the null font; real fonts start at 1.
19#[derive(PartialEq, Eq, Debug, Copy, Clone, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct FontId(pub u32);
22
23impl FontId {
24    /// The null font.
25    pub const NULL: FontId = FontId(0);
26
27    /// The first non-null font ID.
28    pub const ONE: FontId = FontId(1);
29}
30
31impl Default for FontId {
32    /// The default font ID is the null font,
33    /// matching TeX's behavior for font variables that have not been set
34    /// (TeX.2021.222).
35    fn default() -> Self {
36        FontId::NULL
37    }
38}
39
40impl std::fmt::Display for FontId {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "{}", self.0)
43    }
44}
45
46/// Scaled numbers.
47///
48/// This is a fixed-width numeric type used in throughout TeX.
49/// This type is defined and described in part 7 "arithmetic with scaled
50/// dimensions" starting at TeX.2021.99.
51///
52/// This numeric type has 15 bits for the integer part,
53/// 16 bits for the fractional part, and a single signed bit.
54/// The inner value is the number multiplied by 2^16.
55#[derive(Default, PartialEq, Eq, Debug, Copy, Clone, PartialOrd, Ord, Hash)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct Scaled(pub i32);
58
59impl Scaled {
60    /// Representation of the number 0 as a [Scaled].
61    pub const ZERO: Scaled = Scaled(0);
62
63    /// Representation of the number 1 as a [Scaled].
64    pub const ONE: Scaled = Scaled(1 << 16);
65
66    /// Representation of the number 2 as a [Scaled].
67    pub const TWO: Scaled = Scaled(1 << 17);
68
69    pub fn is_zero(&self) -> bool {
70        *self == Scaled::ZERO
71    }
72
73    /// Maximum possible dimension in TeX, which is (2^30-1)/2^16.
74    ///
75    /// This is _not_ the maximum size of the Rust scaled number type, which is (2^31-1)/2^16.
76    ///
77    /// Defined in TeX.2021.421.
78    pub const MAX_DIMEN: Scaled = Scaled((1 << 30) - 1);
79
80    /// Create a scaled number corresponding the provided positive integer.
81    ///
82    /// Scaled numbers are in the range `(-2^14, 2^14)`.
83    /// If _i_ is outside this range an overflow error is returned.
84    pub fn from_integer(i: i32) -> Result<Scaled, OverflowError> {
85        if i >= (1 << 14) || i <= -(1 << 14) {
86            Err(OverflowError {})
87        } else {
88            Ok(Scaled(Scaled::ONE.0 * i))
89        }
90    }
91
92    /// Creates a scaled number from a decimal fraction.
93    ///
94    /// TeX.2021.102.
95    pub fn from_decimal_digits(digits: &[u8]) -> Scaled {
96        let mut a = 0;
97        for d in digits.iter().rev() {
98            a = (a + (*d as i32) * Scaled::TWO.0) / 10
99        }
100        Scaled((a + 1) / 2)
101    }
102
103    /// Creates a scaled number from the provided components.
104    ///
105    /// TeX.2021.458
106    pub fn new(
107        integer_part: i32,
108        fractional_part: Scaled,
109        scaled_unit: ScaledUnit,
110    ) -> Result<Scaled, OverflowError> {
111        if scaled_unit == ScaledUnit::ScaledPoint {
112            return if integer_part > Scaled::MAX_DIMEN.0 {
113                Err(OverflowError)
114            } else {
115                // For sp units, the fractional part is silently dropped.
116                Ok(Scaled(integer_part))
117            };
118        }
119        let (n, d) = scaled_unit.conversion_fraction();
120        // xn_over_d, but with integer arguments
121        let (Scaled(i), Scaled(remainder)) = Scaled(integer_part).xn_over_d(n, d)?;
122        let f =
123            fractional_part.nx_plus_y(n, Scaled::from_integer(remainder).expect("remainder<d<=7200<2^13, so a valid scaled number"))
124            .expect("fractional_part<2^16, remainder<2^16*d, so nx_plus_y<2^16(n+d). Each (n,d) makes this <2^30")
125            / d;
126        let integer_part = Scaled::from_integer(i + f.integer_part())?;
127        Ok(integer_part + f.fractional_part())
128    }
129
130    /// Calculates the integer division _xn_/_d_ and remainder, where _x_ is this scaled number
131    /// and _n_ and _d_ are integers in the range `[0,2^16]`.
132    ///
133    /// This function appears in TeX.2021.107. Knuth is working with 32-bit integers
134    /// and so calculating this number is tricky without overflowing. E.g. _xn_ may
135    /// be larger than `2^32-1` even if the final result is in range.
136    /// TeX has an algorithm that calculates the exact value without overflowing,
137    /// in the case when the final result is in range.
138    ///
139    /// Our implementation simply uses 64-bit integers.
140    pub fn xn_over_d(&self, n: i32, d: i32) -> Result<(Scaled, Scaled), OverflowError> {
141        debug_assert!(n <= 0o200000);
142        debug_assert!(d <= 0o200000);
143        let mut b: i64 = self.0.into();
144        b *= n as i64; // can't overflow because |b|<=2^31 and |n|<=2^16
145        let remainder: i32 = (b % (d as i64)).try_into().expect("d<=2^16 so b%d<2^16");
146        b /= d as i64;
147        if b < -(Scaled::MAX_DIMEN.0 as i64) || b > Scaled::MAX_DIMEN.0 as i64 {
148            return Err(OverflowError {});
149        }
150        let b: i32 = b.try_into().expect("b in (-2^30, +2^30)");
151        Ok((Scaled(b), Scaled(remainder)))
152    }
153
154    /// TeX.2021.105
155    pub fn nx_plus_y(self, mut n: i32, y: Scaled) -> Result<Scaled, OverflowError> {
156        let max_answer = Scaled::MAX_DIMEN;
157        if n == 0 {
158            return Ok(y);
159        }
160        let mut x = self;
161        if n < 0 {
162            n = -n;
163            x = -x;
164        }
165        if x <= (max_answer - y) / n && -x <= (max_answer + y) / n {
166            Ok(x * n + y)
167        } else {
168            Err(OverflowError {})
169        }
170    }
171
172    /// Parses a scaled number from a string of the form `<integer>[.<fraction>]<unit>`.
173    ///
174    /// The unit must be one of the two-character abbreviations recognized by [`ScaledUnit::parse`]
175    /// (e.g. `"pt"`, `"in"`, `"cm"`). The fractional part is optional.
176    pub fn parse_from_string(s: &str) -> Result<Scaled, String> {
177        if s.len() < 3 {
178            return Err(format!(
179                "invalid dimension \"{s}\": expected <number><unit> (e.g. 100pt)"
180            ));
181        }
182        let (value_str, unit_str) = s.split_at(s.len() - 2);
183        let unit = ScaledUnit::parse(unit_str)
184            .ok_or_else(|| format!("invalid unit \"{unit_str}\" in dimension \"{s}\""))?;
185        Self::parse_from_string_with_unit(value_str, unit)
186    }
187
188    fn parse_from_string_with_unit(s: &str, unit: ScaledUnit) -> Result<Scaled, String> {
189        let (int_str, frac_str) = match s.find('.') {
190            Some(pos) => (&s[..pos], &s[pos + 1..]),
191            None => (s, ""),
192        };
193        let integer_part: i32 = int_str
194            .parse()
195            .map_err(|_| format!("invalid number \"{int_str}\" in dimension \"{s}{unit}\""))?;
196        let frac_digits: Vec<u8> = frac_str.chars().map(|c| c as u8 - b'0').collect();
197        if frac_digits.iter().any(|&d| d > 9) {
198            return Err(format!(
199                "invalid fractional part \"{frac_str}\" in dimension \"{s}{unit}\""
200            ));
201        }
202        let fractional_part = Scaled::from_decimal_digits(&frac_digits);
203        Scaled::new(integer_part, fractional_part, unit)
204            .map_err(|_| format!("dimension \"{s}{unit}\" is out of range"))
205    }
206
207    /// Parses a scaled number from the unitless decimal format printed by TeX.
208    ///
209    /// This is the format produced by TeX's `print_scaled` routine
210    ///     (TeX.2021.103) and by [`Scaled::display_no_units`]:
211    ///     an optional minus sign, an integer part, a decimal point,
212    ///     and a non-empty fractional part, with no unit suffix.
213    /// For example: `1.0`, `-0.27779`.
214    /// Dimensions appear in this format in TeX's diagnostic output,
215    ///     such as the box dumps produced by `\showbox`.
216    /// To parse a dimension with a unit suffix like `72.27pt`,
217    ///     use [`Scaled::parse_from_string`] instead.
218    ///
219    /// An overflow error is returned if the number is too large to
220    ///     be represented as a scaled number.
221    ///
222    /// # Panics
223    ///
224    /// Panics if the string is not of the expected format.
225    pub fn parse_no_units(s: &str) -> Result<Scaled, OverflowError> {
226        let (neg, s) = match s.strip_prefix('-') {
227            Some(s) => (true, s),
228            None => (false, s),
229        };
230        let mut parts = s.split('.');
231        let i: i32 = parts
232            .next()
233            .expect("scaled has an integer part")
234            .parse()
235            .expect("integer part is an integer");
236        let mut f = [0_u8; 17];
237        for (k, c) in parts
238            .next()
239            .expect("scaled has a fractional part")
240            .chars()
241            .enumerate()
242        {
243            f[k] = c
244                .to_digit(10)
245                .expect("fractional part are digits")
246                .try_into()
247                .expect("digits are in the range [0,10) and always fit in u8");
248        }
249        let f = Scaled::from_decimal_digits(&f);
250        let sc = Scaled::new(i, f, ScaledUnit::Point)?;
251        Ok(if neg { -sc } else { sc })
252    }
253
254    pub fn integer_part(self) -> i32 {
255        self.0 / Scaled::ONE.0
256    }
257
258    pub fn fractional_part(self) -> Scaled {
259        self % Scaled::ONE.0
260    }
261
262    pub fn abs(self) -> Scaled {
263        Scaled(self.0.abs())
264    }
265
266    pub fn wrapping_add(self, rhs: Scaled) -> Self {
267        Scaled(self.0.wrapping_add(rhs.0))
268    }
269    pub fn checked_add(self, rhs: Scaled) -> Option<Self> {
270        Some(Scaled(self.0.checked_add(rhs.0)?))
271    }
272    pub fn wrapping_mul(self, rhs: i32) -> Self {
273        Scaled(self.0.wrapping_mul(rhs))
274    }
275    pub fn checked_mul(self, rhs: i32) -> Option<Self> {
276        // TODO: need to really probe the overflow behavior here!
277        // I actually think it's correct, but we should add tests.
278        self.nx_plus_y(rhs, Scaled::ZERO).ok()
279    }
280    pub fn checked_div(self, rhs: i32) -> Option<Self> {
281        Some(Scaled(self.0.checked_div(rhs)?))
282    }
283    // TeX.2021.103 print_scaled
284    pub fn display_no_units(self) -> impl std::fmt::Display {
285        struct D {
286            s: Scaled,
287        }
288        impl std::fmt::Display for D {
289            fn fmt(&self, fm: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290                if self.s.0 < 0 {
291                    write!(fm, "-")?;
292                }
293                write!(fm, "{}.", self.s.integer_part().abs())?;
294                // Fractional part
295                let mut f = self.s.fractional_part().abs() * 10 + Scaled(5);
296                let mut delta = Scaled(10);
297                loop {
298                    if delta > Scaled::ONE {
299                        // round the last digit
300                        f += Scaled(0o100000 - 50000);
301                    }
302                    fm.write_char(
303                        char::from_digit(f.integer_part().try_into().unwrap(), 10).unwrap(),
304                    )?;
305                    f = f.fractional_part() * 10;
306                    delta = delta * 10;
307                    if f <= delta {
308                        break;
309                    }
310                }
311                Ok(())
312            }
313        }
314        D { s: self }
315    }
316}
317
318#[derive(Debug, PartialEq, Eq)]
319pub struct OverflowError;
320
321impl std::fmt::Display for Scaled {
322    fn fmt(&self, fm: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        // Integer part
324        write!(fm, "{}", self.display_no_units())?;
325        // Units
326        write!(fm, "pt")?;
327        Ok(())
328    }
329}
330
331impl std::iter::Sum for Scaled {
332    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
333        Self(iter.map(|s| s.0).sum())
334    }
335}
336
337impl std::ops::Add<Scaled> for Scaled {
338    type Output = Scaled;
339    fn add(self, rhs: Scaled) -> Self::Output {
340        Scaled(self.0 + rhs.0)
341    }
342}
343
344impl std::ops::AddAssign<Scaled> for Scaled {
345    fn add_assign(&mut self, rhs: Scaled) {
346        self.0 += rhs.0;
347    }
348}
349
350impl std::ops::Sub<Scaled> for Scaled {
351    type Output = Scaled;
352    fn sub(self, rhs: Scaled) -> Self::Output {
353        Scaled(self.0 - rhs.0)
354    }
355}
356
357impl std::ops::SubAssign<Scaled> for Scaled {
358    fn sub_assign(&mut self, rhs: Scaled) {
359        self.0 -= rhs.0;
360    }
361}
362
363impl std::ops::Mul<i32> for Scaled {
364    type Output = Scaled;
365    fn mul(self, rhs: i32) -> Self::Output {
366        Scaled(self.0 * rhs)
367    }
368}
369
370impl std::ops::Div<i32> for Scaled {
371    type Output = Scaled;
372    fn div(self, rhs: i32) -> Self::Output {
373        Scaled(self.0 / rhs)
374    }
375}
376
377impl std::ops::DivAssign<i32> for Scaled {
378    fn div_assign(&mut self, rhs: i32) {
379        self.0 = self.0 / rhs
380    }
381}
382
383impl std::ops::Rem<i32> for Scaled {
384    type Output = Scaled;
385    fn rem(self, rhs: i32) -> Self::Output {
386        Scaled(self.0 % rhs)
387    }
388}
389
390impl std::ops::Neg for Scaled {
391    type Output = Scaled;
392    fn neg(self) -> Self::Output {
393        Scaled(-self.0)
394    }
395}
396
397/// Unit used to define a scaled integer
398///
399/// Defined in TeX.2021.458 and chapter 10 of the TeX book.
400#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401pub enum ScaledUnit {
402    Point,
403    Pica,
404    Inch,
405    BigPoint,
406    Centimeter,
407    Millimeter,
408    DidotPoint,
409    Cicero,
410    ScaledPoint,
411}
412
413impl std::fmt::Display for ScaledUnit {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        write!(f, "{}", self.abbreviation())
416    }
417}
418
419impl ScaledUnit {
420    /// Returns the unit's two-character abbreviation e.g. `pt` or `in`.
421    pub fn abbreviation(&self) -> &'static str {
422        use ScaledUnit::*;
423        match self {
424            Point => "pt",
425            Pica => "pc",
426            Inch => "in",
427            BigPoint => "bp",
428            Centimeter => "cm",
429            Millimeter => "mm",
430            DidotPoint => "dd",
431            Cicero => "cc",
432            ScaledPoint => "sp",
433        }
434    }
435
436    /// Parses a unit from a two character abbreviation.
437    ///
438    /// E.g., `"pc"` is parsed to [`ScaledUnit::Pica`].
439    /// These are abbreviations are defined in TeX.2021.458 and chapter 10 of the TeX book.
440    pub fn parse(s: &str) -> Option<Self> {
441        use ScaledUnit::*;
442        Some(match s {
443            "pt" => Point,
444            "pc" => Pica,
445            "in" => Inch,
446            "bp" => BigPoint,
447            "cm" => Centimeter,
448            "mm" => Millimeter,
449            "dd" => DidotPoint,
450            "cc" => Cicero,
451            "sp" => ScaledPoint,
452            _ => return None,
453        })
454    }
455
456    /// Returns the fraction needed to convert to/from this unit to points.
457    ///
458    /// The return value is of the form (_n_, _d_).
459    /// If a scaled number represents _x_in these units (e.g. y [`ScaledUnit::Pica`]),
460    ///     then it is _y_=_nx_/_d_ points.
461    ///
462    /// Defined in TeX.2021.458.
463    pub fn conversion_fraction(&self) -> (i32, i32) {
464        use ScaledUnit::*;
465        match self {
466            Point => (1, 1),
467            Pica => (12, 1),
468            Inch => (7227, 100),
469            BigPoint => (7227, 7200),
470            Centimeter => (7227, 254),
471            Millimeter => (7227, 2540),
472            DidotPoint => (1238, 1157),
473            Cicero => (14856, 1157),
474            ScaledPoint => (1, 1 << 16),
475        }
476    }
477}
478
479/// Glue.
480///
481/// In Knuth's TeX this struct is not passed around directly; instead
482/// Knuth essentially uses `std::rc::Rc<Glue>`.
483/// This optimization is based on the fact that very few distinct glue
484/// values appear in a document, and that the pointer takes up less
485/// space than the struct.
486/// We might consider performing such an optimization.
487///
488/// Described in TeX.2021.150.
489#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
490#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
491pub struct Glue {
492    pub width: Scaled,
493    pub stretch: Scaled,
494    pub stretch_order: GlueOrder,
495    pub shrink: Scaled,
496    pub shrink_order: GlueOrder,
497}
498
499impl Glue {
500    pub const ZERO: Glue = Glue {
501        width: Scaled::ZERO,
502        stretch: Scaled::ZERO,
503        stretch_order: GlueOrder::Normal,
504        shrink: Scaled::ZERO,
505        shrink_order: GlueOrder::Normal,
506    };
507    pub fn is_zero(&self) -> bool {
508        self.width == Scaled::ZERO && self.stretch == Scaled::ZERO && self.shrink == Scaled::ZERO
509    }
510}
511
512impl std::ops::Mul<i32> for Glue {
513    type Output = Glue;
514    fn mul(self, rhs: i32) -> Self::Output {
515        Glue {
516            width: self.width * rhs,
517            stretch: self.stretch * rhs,
518            stretch_order: self.stretch_order,
519            shrink: self.shrink * rhs,
520            shrink_order: self.shrink_order,
521        }
522    }
523}
524
525impl Glue {
526    /// TeX.2021.1239
527    pub fn wrapping_add(self, rhs: Glue) -> Self {
528        use std::cmp::Ordering::*;
529        Glue {
530            width: self.width.wrapping_add(rhs.width),
531            stretch: match self.stretch_order.cmp(&rhs.stretch_order) {
532                Less => rhs.stretch,
533                Equal => self.stretch.wrapping_add(rhs.stretch),
534                Greater => self.stretch,
535            },
536            stretch_order: self.stretch_order.max(rhs.stretch_order),
537            shrink: match self.shrink_order.cmp(&rhs.shrink_order) {
538                Less => rhs.shrink,
539                Equal => self.shrink.wrapping_add(rhs.shrink),
540                Greater => self.shrink,
541            },
542            shrink_order: self.shrink_order.max(rhs.shrink_order),
543        }
544    }
545    pub fn checked_add(self, rhs: Glue) -> Option<Self> {
546        use std::cmp::Ordering::*;
547        Some(Glue {
548            width: self.width.checked_add(rhs.width)?,
549            stretch: match self.stretch_order.cmp(&rhs.stretch_order) {
550                Less => rhs.stretch,
551                Equal => self.stretch.checked_add(rhs.stretch)?,
552                Greater => self.stretch,
553            },
554            stretch_order: self.stretch_order.max(rhs.stretch_order),
555            shrink: match self.shrink_order.cmp(&rhs.shrink_order) {
556                Less => rhs.shrink,
557                Equal => self.shrink.checked_add(rhs.shrink)?,
558                Greater => self.shrink,
559            },
560            shrink_order: self.shrink_order.max(rhs.shrink_order),
561        })
562    }
563    pub fn checked_mul(self, rhs: i32) -> Option<Self> {
564        Some(Glue {
565            width: self.width.checked_mul(rhs)?,
566            stretch: self.stretch.checked_mul(rhs)?,
567            stretch_order: self.stretch_order,
568            shrink: self.shrink.checked_mul(rhs)?,
569            shrink_order: self.shrink_order,
570        })
571    }
572    pub fn wrapping_mul(self, rhs: i32) -> Self {
573        Glue {
574            width: self.width.wrapping_mul(rhs),
575            stretch: self.stretch.wrapping_mul(rhs),
576            stretch_order: self.stretch_order,
577            shrink: self.shrink.wrapping_mul(rhs),
578            shrink_order: self.shrink_order,
579        }
580    }
581    pub fn checked_div(self, rhs: i32) -> Option<Self> {
582        Some(Glue {
583            width: self.width.checked_div(rhs)?,
584            stretch: self.stretch.checked_div(rhs)?,
585            stretch_order: self.stretch_order,
586            shrink: self.shrink.checked_div(rhs)?,
587            shrink_order: self.shrink_order,
588        })
589    }
590
591    pub fn parse_from_string(s: &str) -> Result<Glue, String> {
592        let mut glue = Glue::ZERO;
593        let (width_str, rest) = match s.find(" plus ").or_else(|| s.find(" minus ")) {
594            Some(pos) => (&s[..pos], s[pos..].trim()),
595            None => (s, ""),
596        };
597        glue.width = Scaled::parse_from_string(width_str.trim())?;
598        let rest = if let Some(r) = rest.strip_prefix("plus ") {
599            let (stretch_str, minus_rest) = match r.find(" minus ") {
600                Some(pos) => (&r[..pos], r[pos..].trim()),
601                None => (r, ""),
602            };
603            let (stretch, order) = Glue::parse_scaled_inf(stretch_str.trim())?;
604            glue.stretch = stretch;
605            glue.stretch_order = order;
606            minus_rest
607        } else {
608            rest
609        };
610        if let Some(shrink_str) = rest.strip_prefix("minus ") {
611            let (shrink, order) = Glue::parse_scaled_inf(shrink_str.trim())?;
612            glue.shrink = shrink;
613            glue.shrink_order = order;
614        } else if !rest.is_empty() {
615            return Err(format!("invalid glue {s:?}"));
616        }
617        Ok(glue)
618    }
619
620    fn parse_scaled_inf(s: &str) -> Result<(Scaled, GlueOrder), String> {
621        for (suffix, order) in [
622            ("filll", GlueOrder::Filll),
623            ("fill", GlueOrder::Fill),
624            ("fil", GlueOrder::Fil),
625        ] {
626            if let Some(num_str) = s.strip_suffix(suffix) {
627                return Ok((
628                    Scaled::parse_from_string_with_unit(num_str, ScaledUnit::Point)?,
629                    order,
630                ));
631            }
632        }
633        Ok((Scaled::parse_from_string(s)?, GlueOrder::Normal))
634    }
635}
636
637impl std::fmt::Display for Glue {
638    // TeX.2021.177 print_spec with s="pt"
639    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640        write!(f, "{}", self.width)?;
641        if self.stretch != Scaled::ZERO {
642            write!(f, " plus ")?;
643            write!(f, "{}", self.stretch.display_no_units())?;
644            write!(f, "{}", self.stretch_order)?;
645        }
646        if self.shrink != Scaled::ZERO {
647            write!(f, " minus ")?;
648            write!(f, "{}", self.shrink.display_no_units())?;
649            write!(f, "{}", self.shrink_order)?;
650        }
651        Ok(())
652    }
653}
654
655/// Order of infinity of a glue stretch or shrink.
656///
657/// When setting a list of boxes, TeX stretches or shrinks glue boxes.
658/// In some cases it is desirable that TeX only stretches some subset of the
659/// glue boxes.
660/// For example, when setting centered text, TeX only stretches the two glue
661/// boxes at each end of the list and leaves all other glue intact.
662///
663/// To achieve this, each glue stretch or shrink has an order of infinity.
664/// If a list contains glue of some order (e.g. [GlueOrder::Fil]),
665/// then glues of a lower order (e.g. [GlueOrder::Normal]) are not stretched
666/// or shrunk.
667#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PartialOrd, Ord)]
668#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
669pub enum GlueOrder {
670    #[default]
671    Normal,
672    Fil,
673    Fill,
674    Filll,
675}
676
677impl GlueOrder {
678    /// Parses an infinite glue order from a keyword.
679    pub fn parse(s: &str) -> Option<Self> {
680        use GlueOrder::*;
681        Some(match s {
682            "fil" => Fil,
683            "fill" => Fill,
684            "filll" => Filll,
685            _ => return None,
686        })
687    }
688    pub fn inf_str(&self) -> Option<&'static str> {
689        use GlueOrder::*;
690        match self {
691            Normal => None,
692            Fil => Some("fil"),
693            Fill => Some("fill"),
694            Filll => Some("filll"),
695        }
696    }
697    /// Returns the next highest glue order.
698    pub fn next(&self) -> Option<Self> {
699        use GlueOrder::*;
700        match self {
701            Normal => Some(Fil),
702            Fil => Some(Fill),
703            Fill => Some(Filll),
704            Filll => None,
705        }
706    }
707}
708
709impl std::fmt::Display for GlueOrder {
710    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
711        write!(f, "{}", self.inf_str().unwrap_or("pt"))
712    }
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    #[test]
720    fn type_sizes() {
721        assert_eq!(16, std::mem::size_of::<Glue>());
722    }
723
724    macro_rules! parse_from_string_tests {
725        ( $( $name:ident : $input:expr => $expected:expr, )* ) => {
726            $(
727                #[test]
728                fn $name() {
729                    assert_eq!(Scaled::parse_from_string($input), $expected);
730                }
731            )*
732        };
733    }
734
735    parse_from_string_tests! {
736        // Basic unit types
737        integer_pt:    "100pt"  => Ok(Scaled::new(100, Scaled::ZERO, ScaledUnit::Point).unwrap()),
738        integer_pc:    "6pc"    => Ok(Scaled::new(6,   Scaled::ZERO, ScaledUnit::Pica).unwrap()),
739        integer_in:    "2in"    => Ok(Scaled::new(2,   Scaled::ZERO, ScaledUnit::Inch).unwrap()),
740        integer_bp:    "72bp"   => Ok(Scaled::new(72,  Scaled::ZERO, ScaledUnit::BigPoint).unwrap()),
741        integer_cm:    "10cm"   => Ok(Scaled::new(10,  Scaled::ZERO, ScaledUnit::Centimeter).unwrap()),
742        integer_mm:    "25mm"   => Ok(Scaled::new(25,  Scaled::ZERO, ScaledUnit::Millimeter).unwrap()),
743        integer_dd:    "10dd"   => Ok(Scaled::new(10,  Scaled::ZERO, ScaledUnit::DidotPoint).unwrap()),
744        integer_cc:    "3cc"    => Ok(Scaled::new(3,   Scaled::ZERO, ScaledUnit::Cicero).unwrap()),
745        integer_sp:    "65536sp" => Ok(Scaled(65536)),
746        // Fractional part
747        fractional_pt: "72.27pt" => Ok(Scaled::new(72, Scaled::from_decimal_digits(&[2, 7]), ScaledUnit::Point).unwrap()),
748        fractional_in: "6.5in"  => Ok(Scaled::new(6,  Scaled::from_decimal_digits(&[5]),    ScaledUnit::Inch).unwrap()),
749        // Zero
750        zero_pt:       "0pt"    => Ok(Scaled::ZERO),
751        // Error cases
752        empty:         ""       => Err("invalid dimension \"\": expected <number><unit> (e.g. 100pt)".to_string()),
753        bad_unit:      "10xx"   => Err("invalid unit \"xx\" in dimension \"10xx\"".to_string()),
754        bad_number:    "abpt"   => Err("invalid number \"ab\" in dimension \"abpt\"".to_string()),
755        bad_fraction:  "1.xpt"  => Err("invalid fractional part \"x\" in dimension \"1.xpt\"".to_string()),
756    }
757
758    macro_rules! parse_no_units_tests {
759        ( $( $name:ident : $input:expr => $expected:expr, )* ) => {
760            $(
761                #[test]
762                fn $name() {
763                    assert_eq!(Scaled::parse_no_units($input), $expected);
764                }
765            )*
766        };
767    }
768
769    parse_no_units_tests! {
770        no_units_one:      "1.0"      => Ok(Scaled::ONE),
771        no_units_zero:     "0.0"      => Ok(Scaled::ZERO),
772        no_units_negative: "-2.5"     => Ok(-(Scaled::ONE * 5) / 2),
773        no_units_fraction: "0.27779"  => Ok(Scaled::from_decimal_digits(&[2, 7, 7, 7, 9])),
774        no_units_overflow: "16384.0"  => Err(OverflowError),
775    }
776
777    #[test]
778    fn parse_no_units_round_trips_display_no_units() {
779        for sc in [Scaled(1), Scaled(-1), Scaled(18205), Scaled::MAX_DIMEN] {
780            let printed = format!("{}", sc.display_no_units());
781            assert_eq!(Scaled::parse_no_units(&printed), Ok(sc));
782        }
783    }
784
785    #[test]
786    fn print_smallest_scaled() {
787        assert_eq!("-32768.0pt", format!("{}", Scaled(i32::MIN)));
788    }
789
790    #[test]
791    fn glue_order_ordering() {
792        assert!(GlueOrder::Normal < GlueOrder::Fil);
793        assert!(GlueOrder::Fil < GlueOrder::Fill);
794        assert!(GlueOrder::Fill < GlueOrder::Filll);
795    }
796}