common/
lib.rs

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