texlang/parse/
dimen.rs

1use common::Scaled;
2
3use super::keyword::parse_keyword;
4use crate::prelude as txl;
5use crate::token::Value;
6use crate::traits::*;
7use crate::*;
8
9impl Parsable for Scaled {
10    fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
11        scan_dimen(input, None)
12    }
13}
14
15// TeX.2021.448 scan_dimen with:
16//
17// - shortcut=false
18// - the inf parameter is replaced by an optional pointer to a glue order
19pub(crate) fn scan_dimen<S: TexlangState>(
20    input: &mut vm::ExpandedStream<S>,
21    glue_order: Option<&mut common::GlueOrder>,
22) -> txl::Result<common::Scaled> {
23    let negative = match super::integer::parse_optional_signs(input)? {
24        None => 1,
25        Some(_) => -1,
26    };
27    let first_token = input.next_or_err(DimenEndOfInputError {})?;
28    let (negative, integer_part, fractional_part) = match first_token.value() {
29        Value::CommandRef(command_ref) => {
30            // TeX.2021.449
31            use super::integer::InternalNumber;
32            match super::integer::parse_internal_number(input, first_token, command_ref)? {
33                InternalNumber::Integer(i) => (negative * i.signum(), i.abs(), Scaled::ZERO),
34                InternalNumber::Dimen(d) => {
35                    return Ok(d * negative);
36                }
37                InternalNumber::Glue(g) => {
38                    return Ok(g.width * negative);
39                }
40            }
41        }
42        _ => {
43            let (i, f) = scan_constant_dimen(input, first_token)?;
44            (negative, i, f)
45        }
46    };
47    Ok(scan_and_apply_units(
48        input,
49        first_token,
50        integer_part,
51        fractional_part,
52        glue_order,
53    )? * negative)
54}
55
56/// Part of TeX.2021.448
57pub(crate) fn scan_constant_dimen<S: TexlangState>(
58    input: &mut vm::ExpandedStream<S>,
59    first_token: token::Token,
60) -> txl::Result<(i32, common::Scaled)> {
61    Ok(match first_token.value() {
62        Value::Other(',' | '.') => (0, scan_decimal_fraction(input)?),
63        _ => {
64            input.back(first_token);
65            let (_, i, radix) = super::integer::parse_integer(input)?;
66            // We scan for a fractional part if the integer was an decimal constant
67            // and the next token is a period or comma.
68            let fractional_part = if radix == Some(10) {
69                match input.next()? {
70                    Some(next) => match next.value() {
71                        Value::Other(',' | '.') => scan_decimal_fraction(input)?,
72                        _ => {
73                            input.back(next);
74                            Scaled::ZERO
75                        }
76                    },
77                    None => Scaled::ZERO,
78                }
79            } else {
80                Scaled::ZERO
81            };
82            (i, fractional_part)
83        }
84    })
85}
86
87/// TeX.2021.453
88pub(crate) fn scan_and_apply_units<S: TexlangState>(
89    input: &mut vm::ExpandedStream<S>,
90    first_token: token::Token,
91    integer_part: i32,
92    fractional_part: Scaled,
93    glue_order: Option<&mut common::GlueOrder>,
94) -> txl::Result<common::Scaled> {
95    // todo: scan spaces and non-call tokens
96
97    // First try to scan for infinite units, if this is the stretch
98    // or shrink of a glue.
99    if let Some(glue_order) = glue_order {
100        if parse_keyword(input, "fil")? {
101            *glue_order = common::GlueOrder::Fil;
102            while let Some(token) = input.next()? {
103                match token.value() {
104                    token::Value::Letter('l' | 'L') => match glue_order.next() {
105                        None => {
106                            input.error(fillll_error(token))?;
107                        }
108                        Some(o) => *glue_order = o,
109                    },
110                    _ => {
111                        input.back(token);
112                        break;
113                    }
114                }
115            }
116            super::OptionalSpace::parse(input)?;
117            return match Scaled::from_integer(integer_part) {
118                Ok(integer_part) => Ok(integer_part + fractional_part),
119                Err(_) => handle_overflow(input, first_token, false),
120            };
121        }
122    }
123    // Then try to scan from an internal number.
124    // TeX.2021.455
125    if let Some(next) = input.next()? {
126        let v_or = match next.value() {
127            Value::CommandRef(command_ref) => {
128                use super::integer::InternalNumber;
129                Some(
130                    match super::integer::parse_internal_number(input, next, command_ref)? {
131                        // TeX silently interprets an integer as sp points in this position
132                        InternalNumber::Integer(i) => Scaled(i),
133                        InternalNumber::Dimen(scaled) => scaled,
134                        InternalNumber::Glue(glue) => glue.width,
135                    },
136                )
137            }
138            _ => {
139                input.back(next);
140                if parse_keyword(input, "em")? {
141                    super::OptionalSpace::parse(input)?;
142                    Some(input.state().em_width())
143                } else if parse_keyword(input, "ex")? {
144                    super::OptionalSpace::parse(input)?;
145                    Some(input.state().ex_height())
146                } else {
147                    None
148                }
149            }
150        };
151        if let Some(v) = v_or {
152            let adjusted_fractional_part = v
153                .xn_over_d(fractional_part.0, Scaled::ONE.0)
154                .expect("n<d=Scaled::ONE, so overflow can't occur");
155            return match v.nx_plus_y(integer_part, adjusted_fractional_part.0) {
156                Ok(s) => Ok(s),
157                Err(_) => handle_overflow(input, first_token, v < Scaled::ZERO),
158            };
159        }
160    }
161
162    // Potentially adjust for the magnification ratio.
163    if parse_keyword(input, "true")? {
164        // TeX.2021.457
165        // TODO
166    }
167
168    // Finally try to scan unit constants.
169    let scaled_unit = <common::ScaledUnit as Parsable>::parse(input)?;
170    super::OptionalSpace::parse(input)?;
171    match Scaled::new(integer_part, fractional_part, scaled_unit) {
172        Ok(s) => Ok(s),
173        Err(_) => handle_overflow(input, first_token, false),
174    }
175}
176
177fn handle_overflow<S: TexlangState>(
178    input: &mut vm::ExpandedStream<S>,
179    first_token: token::Token,
180    neg: bool,
181) -> txl::Result<common::Scaled> {
182    input.error(
183        parse::Error::new(
184            "a dimension in the range (-2^14pt,2^14pt)",
185            Some(first_token),
186            "",
187        )
188        .with_got_override("a dimension that's too large"),
189    )?;
190    Ok(if neg {
191        -common::Scaled::MAX_DIMEN
192    } else {
193        common::Scaled::MAX_DIMEN
194    })
195}
196
197fn fillll_error(token: token::Token) -> parse::Error {
198    parse::Error::new("an infinite glue stretch or shrink order", Some(token), "")
199        .with_got_override("too many l characters")
200}
201
202impl Parsable for common::ScaledUnit {
203    fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
204        use common::ScaledUnit;
205        for (keyword, unit) in [
206            ("pt", ScaledUnit::Point),
207            ("in", ScaledUnit::Inch),
208            ("pc", ScaledUnit::Pica),
209            ("cm", ScaledUnit::Centimeter),
210            ("mm", ScaledUnit::Millimeter),
211            ("bp", ScaledUnit::BigPoint),
212            ("dd", ScaledUnit::DidotPoint),
213            ("cc", ScaledUnit::Cicero),
214            ("sp", ScaledUnit::ScaledPoint),
215        ] {
216            if parse_keyword(input, keyword)? {
217                return Ok(unit);
218            }
219        }
220        input.error(error::TODO())?;
221        Ok(common::ScaledUnit::Point)
222    }
223}
224
225/// TeX.2021.452
226fn scan_decimal_fraction<S: TexlangState>(
227    input: &mut vm::ExpandedStream<S>,
228) -> txl::Result<Scaled> {
229    // We only get up to 17 digits, because further digits won't affect the result given
230    // that the smallest scaled number is 2^(-16). This is very nice because it means
231    // we don't need to allocate a vector to store the digits.
232    let mut digits = [0_u8; 17];
233    let mut i = 0_usize;
234    while let Some(token) = input.next()? {
235        let d: u8 = match token.value() {
236            Value::Other('0') => 0,
237            Value::Other('1') => 1,
238            Value::Other('2') => 2,
239            Value::Other('3') => 3,
240            Value::Other('4') => 4,
241            Value::Other('5') => 5,
242            Value::Other('6') => 6,
243            Value::Other('7') => 7,
244            Value::Other('8') => 8,
245            Value::Other('9') => 9,
246            Value::Space(_) => {
247                break;
248            }
249            _ => {
250                input.back(token);
251                break;
252            }
253        };
254        if let Some(digit) = digits.get_mut(i) {
255            *digit = d;
256            i += 1;
257        }
258    }
259    Ok(Scaled::from_decimal_digits(&digits[0..i]))
260}
261
262#[derive(Debug)]
263struct DimenEndOfInputError;
264
265impl error::EndOfInputError for DimenEndOfInputError {
266    fn doing(&self) -> String {
267        "parsing a dimension".into()
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use crate::parse::testing::*;
275
276    #[derive(Default)]
277    struct State;
278
279    impl TexlangState for State {}
280
281    parse_success_tests![
282        (zero_pt, "0pt", Scaled::ZERO),
283        (one_pt, "1pt", Scaled::ONE),
284        (one_pt_negative, "-1pt", -Scaled::ONE),
285        (two_pt, "2pt", Scaled::TWO),
286        (empty_point, ".pt", Scaled::ZERO), // TeX.2021.452
287        (fraction_1, "0.5pt", Scaled::from_decimal_digits(&[5])),
288        (fraction_2, "-0.5pt", -Scaled::from_decimal_digits(&[5])),
289        (
290            fraction_3,
291            "1.5pt",
292            Scaled::ONE + Scaled::from_decimal_digits(&[5])
293        ),
294        (
295            fraction_4,
296            "-1.5pt",
297            -Scaled::ONE - Scaled::from_decimal_digits(&[5])
298        ),
299        (units_in_1, "1in", (Scaled::ONE * 7227) / 100),
300        (units_in_2, "1 in", (Scaled::ONE * 7227) / 100),
301        (units_in_3, "0.075in", Scaled(355207)),
302        (units_pc, "1pc", Scaled::ONE * 12),
303        (units_cm, "1cm", (Scaled::ONE * 7227) / 254),
304        (units_mm, "1mm", (Scaled::ONE * 7227) / 2540),
305        (units_bp, "1bp", (Scaled::ONE * 7227) / 7200),
306        (units_dd, "1dd", (Scaled::ONE * 1238) / 1157),
307        (units_cc, "1cc", (Scaled::ONE * 14856) / 1157),
308        (units_sp_1, "1sp", Scaled(1)),
309        (units_sp_2, "1.999999sp", Scaled(1)),
310        (nearly_overflow_pt, "16383.99998pt", Scaled::MAX_DIMEN,),
311        // (2^30-1) * d / (n * 2^16)
312        // (2^30-1) * d / (n * 2^16)
313        // (nearly_overflow_in, "226.705406in", Scaled::MAX_DIMEN ),
314        (nearly_overflow_sp_1, "1073741823sp", Scaled::MAX_DIMEN,),
315        (
316            nearly_overflow_sp_2,
317            "1073741823.99999999sp",
318            Scaled::MAX_DIMEN,
319        )
320    ];
321
322    parse_failure_tests!(
323        Scaled,
324        State,
325        (invalid_unit, "1xy", Scaled::ONE),
326        (overflow_pt, "16384pt", Scaled::MAX_DIMEN),
327        (overflow_pt_neg, "-16384pt", -Scaled::MAX_DIMEN),
328        (overflow_in_1, "300in", Scaled::MAX_DIMEN),
329        (overflow_in_2, "300000000in", Scaled::MAX_DIMEN),
330        (overflow_in_3, "-300in", -Scaled::MAX_DIMEN),
331        (overflow_in_4, "-300000000in", -Scaled::MAX_DIMEN),
332        (overflow_sp, "1073741824sp", Scaled::MAX_DIMEN),
333        (overflow_sp_neg, "-1073741824sp", -Scaled::MAX_DIMEN),
334    );
335}