Skip to main content

hayro_syntax/object/
number.rs

1//! Numbers.
2
3use crate::math::{powi_f64, trunc_f64};
4use crate::object::macros::object;
5use crate::object::{Object, ObjectLike};
6use crate::reader::Reader;
7use crate::reader::{Readable, ReaderContext, ReaderExt, Skippable};
8use crate::trivia::{is_regular_character, is_white_space_character};
9use core::fmt::{Debug, Display, Formatter};
10
11#[rustfmt::skip]
12static POWERS_OF_10: [f64; 20] = [
13    1.0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
14    1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
15];
16
17/// A number.
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct Number(pub(crate) InternalNumber);
20
21impl Number {
22    /// The number zero.
23    pub const ZERO: Self = Self::from_i32(0);
24    /// The number one.
25    pub const ONE: Self = Self::from_i32(1);
26
27    /// Returns the number as a f64.
28    pub fn as_f64(&self) -> f64 {
29        match self.0 {
30            InternalNumber::Real(r) => r,
31            InternalNumber::Integer(i) => i as f64,
32        }
33    }
34
35    /// Returns the number as a f32.
36    pub fn as_f32(&self) -> f32 {
37        match self.0 {
38            InternalNumber::Real(r) => r as f32,
39            InternalNumber::Integer(i) => i as f32,
40        }
41    }
42
43    /// Returns the number as an i64.
44    pub fn as_i64(&self) -> i64 {
45        match self.0 {
46            InternalNumber::Real(r) => {
47                let res = r as i64;
48
49                if !(trunc_f64(r) == r) {
50                    debug!("float {r} was truncated to {res}");
51                }
52
53                res
54            }
55            InternalNumber::Integer(i) => i,
56        }
57    }
58
59    /// Create a new `Number` from an f32 number.
60    pub const fn from_f32(num: f32) -> Self {
61        Self(InternalNumber::Real(num as f64))
62    }
63
64    /// Create a new `Number` from an i32 number.
65    pub const fn from_i32(num: i32) -> Self {
66        Self(InternalNumber::Integer(num as i64))
67    }
68}
69
70impl Display for Number {
71    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
72        match self.0 {
73            InternalNumber::Real(value) => write!(f, "{value:?}"),
74            InternalNumber::Integer(value) => write!(f, "{value}"),
75        }
76    }
77}
78
79impl Skippable for Number {
80    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
81        let has_sign = r.forward_if(|b| b == b'+' || b == b'-').is_some();
82
83        // Some PDFs have weird trailing minuses, so try to accept those as well.
84        match r.peek_byte()? {
85            b'.' => {
86                r.read_byte()?;
87                // See PDFJS-9252 - treat a single . as 0.
88                r.forward_while(is_digit_or_minus);
89            }
90            b'0'..=b'9' | b'-' => {
91                r.forward_while_1(is_digit_or_minus)?;
92                if let Some(()) = r.forward_tag(b".") {
93                    r.forward_while(is_digit_or_minus);
94                }
95            }
96            // See PDFJS-bug1753983 - accept just + or - as a zero.
97            // ALso see PDFJS-bug1953099, where the sign is followed by a show
98            // text string operand, requiring us to allow '<' and '(' as well.
99            b if has_sign && (is_white_space_character(b) || matches!(b, b'(' | b'<')) => {}
100            _ => return None,
101        }
102
103        // See issue 994. Don't accept numbers that are followed by a regular character.
104        if r.peek_byte().is_some_and(is_regular_character) {
105            return None;
106        }
107
108        Some(())
109    }
110}
111
112impl Readable<'_> for Number {
113    #[inline]
114    fn read(r: &mut Reader<'_>, _: &ReaderContext<'_>) -> Option<Self> {
115        let old_offset = r.offset();
116        read_inner(r).or_else(|| {
117            r.jump(old_offset);
118            None
119        })
120    }
121}
122
123#[inline(always)]
124fn read_inner(r: &mut Reader<'_>) -> Option<Number> {
125    let start_offset = r.offset();
126    let negative = match r.peek_byte()? {
127        b'-' => {
128            r.forward();
129            true
130        }
131        b'+' => {
132            r.forward();
133            false
134        }
135        _ => false,
136    };
137
138    let mut mantissa: u64 = 0;
139    let mut has_dot = false;
140    let mut decimal_shift: u32 = 0;
141    let mut has_digits = false;
142    let mut overflowed = false;
143    let mut number_end = None;
144
145    loop {
146        match r.peek_byte() {
147            Some(b'0'..=b'9') => {
148                let d = r.read_byte().unwrap();
149                if !overflowed {
150                    if let Some(value) = mantissa
151                        .checked_mul(10)
152                        .and_then(|v| v.checked_add((d - b'0') as u64))
153                    {
154                        mantissa = value;
155                    } else {
156                        overflowed = true;
157                    }
158                }
159                has_digits = true;
160                if has_dot {
161                    decimal_shift += 1;
162                }
163            }
164            Some(b'.') if !has_dot => {
165                r.forward();
166                has_dot = true;
167            }
168            // Some weird PDFs have trailing minus in the fraction of number.
169            Some(b'-') if has_digits => {
170                number_end = Some(r.offset());
171                r.forward();
172                r.forward_while(is_digit_or_minus);
173                break;
174            }
175            _ => break,
176        }
177    }
178
179    if !has_digits {
180        if negative || has_dot {
181            // Treat numbers like just `-`, `+` or `-.` as zero.
182            return Some(Number(InternalNumber::Integer(0)));
183        }
184        return None;
185    }
186
187    // See issue 994. Don't accept numbers that are followed by a regular character
188    // without any white space in-between.
189    if r.peek_byte().is_some_and(is_regular_character) {
190        return None;
191    }
192
193    let integer_overflow = !has_dot && mantissa > i64::MAX as u64;
194
195    if overflowed || integer_overflow {
196        let end_offset = number_end.unwrap_or_else(|| r.offset());
197        let number = core::str::from_utf8(r.range(start_offset..end_offset)?).ok()?;
198
199        if !has_dot && let Ok(value) = number.parse::<i64>() {
200            return Some(Number(InternalNumber::Integer(value)));
201        }
202
203        let value = number.parse::<f64>().ok()?;
204        return Some(Number(InternalNumber::Real(value)));
205    }
206
207    if !has_dot {
208        let value = if negative {
209            (mantissa as i64).wrapping_neg()
210        } else {
211            mantissa as i64
212        };
213        Some(Number(InternalNumber::Integer(value)))
214    } else {
215        let mut value = mantissa as f64;
216
217        if decimal_shift > 0 {
218            if decimal_shift < POWERS_OF_10.len() as u32 {
219                value /= POWERS_OF_10[decimal_shift as usize];
220            } else {
221                value /= powi_f64(10.0, decimal_shift);
222            }
223        }
224
225        if negative {
226            value = -value;
227        }
228
229        Some(Number(InternalNumber::Real(value)))
230    }
231}
232
233object!(Number, Number);
234
235#[derive(Clone, Copy, Debug, PartialEq)]
236pub(crate) enum InternalNumber {
237    Real(f64),
238    Integer(i64),
239}
240
241macro_rules! int_num {
242    ($i:ident) => {
243        impl Skippable for $i {
244            fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
245                r.forward_if(|b| b == b'+' || b == b'-');
246                r.forward_while_1(is_digit)?;
247
248                // We have a float instead of an integer.
249                if r.peek_byte() == Some(b'.') {
250                    return None;
251                }
252
253                // See issue 994. Don't accept numbers that are followed by a regular character
254                // without any white space in-between.
255                if r.peek_byte().is_some_and(is_regular_character) {
256                    return None;
257                }
258
259                Some(())
260            }
261        }
262
263        impl<'a> Readable<'a> for $i {
264            fn read(r: &mut Reader<'a>, ctx: &ReaderContext<'a>) -> Option<$i> {
265                r.read::<Number>(ctx)
266                    .map(|n| n.as_i64())
267                    .and_then(|n| n.try_into().ok())
268            }
269        }
270
271        impl TryFrom<Object<'_>> for $i {
272            type Error = ();
273
274            fn try_from(value: Object<'_>) -> core::result::Result<Self, Self::Error> {
275                match value {
276                    Object::Number(n) => n.as_i64().try_into().ok().ok_or(()),
277                    _ => Err(()),
278                }
279            }
280        }
281
282        impl<'a> ObjectLike<'a> for $i {}
283    };
284}
285
286int_num!(i32);
287int_num!(i64);
288int_num!(u32);
289int_num!(u16);
290int_num!(usize);
291int_num!(u8);
292
293impl Skippable for f32 {
294    fn skip(r: &mut Reader<'_>, is_content_stream: bool) -> Option<()> {
295        r.skip::<Number>(is_content_stream).map(|_| {})
296    }
297}
298
299impl Readable<'_> for f32 {
300    fn read(r: &mut Reader<'_>, _: &ReaderContext<'_>) -> Option<Self> {
301        r.read_without_context::<Number>()
302            .map(|n| n.as_f64() as Self)
303    }
304}
305
306impl TryFrom<Object<'_>> for f32 {
307    type Error = ();
308
309    fn try_from(value: Object<'_>) -> Result<Self, Self::Error> {
310        match value {
311            Object::Number(n) => Ok(n.as_f64() as Self),
312            _ => Err(()),
313        }
314    }
315}
316
317impl ObjectLike<'_> for f32 {}
318
319impl Skippable for f64 {
320    fn skip(r: &mut Reader<'_>, is_content_stream: bool) -> Option<()> {
321        r.skip::<Number>(is_content_stream).map(|_| {})
322    }
323}
324
325impl Readable<'_> for f64 {
326    fn read(r: &mut Reader<'_>, _: &ReaderContext<'_>) -> Option<Self> {
327        r.read_without_context::<Number>().map(|n| n.as_f64())
328    }
329}
330
331impl TryFrom<Object<'_>> for f64 {
332    type Error = ();
333
334    fn try_from(value: Object<'_>) -> Result<Self, Self::Error> {
335        match value {
336            Object::Number(n) => Ok(n.as_f64()),
337            _ => Err(()),
338        }
339    }
340}
341
342impl ObjectLike<'_> for f64 {}
343
344pub(crate) fn is_digit(byte: u8) -> bool {
345    byte.is_ascii_digit()
346}
347
348pub(crate) fn is_digit_or_minus(byte: u8) -> bool {
349    is_digit(byte) || byte == b'-'
350}
351
352#[cfg(test)]
353mod tests {
354    use crate::object::Number;
355    use crate::reader::Reader;
356    use crate::reader::ReaderExt;
357
358    #[test]
359    fn display() {
360        assert_eq!(format!("{}", Number::from_i32(10)), "10");
361        assert_eq!(format!("{}", Number::from_f32(10.0)), "10.0");
362        assert_eq!(format!("{}", Number::from_f32(10.5)), "10.5");
363    }
364
365    #[test]
366    fn int_1() {
367        assert_eq!(
368            Reader::new("0".as_bytes())
369                .read_without_context::<i32>()
370                .unwrap(),
371            0
372        );
373    }
374
375    #[test]
376    fn int_3() {
377        assert_eq!(
378            Reader::new("+32".as_bytes())
379                .read_without_context::<i32>()
380                .unwrap(),
381            32
382        );
383    }
384
385    #[test]
386    fn int_4() {
387        assert_eq!(
388            Reader::new("-32".as_bytes())
389                .read_without_context::<i32>()
390                .unwrap(),
391            -32
392        );
393    }
394
395    #[test]
396    fn int_6() {
397        assert_eq!(
398            Reader::new("98349".as_bytes())
399                .read_without_context::<i32>()
400                .unwrap(),
401            98349
402        );
403    }
404
405    #[test]
406    fn int_7() {
407        assert_eq!(
408            Reader::new("003245".as_bytes())
409                .read_without_context::<i32>()
410                .unwrap(),
411            3245
412        );
413    }
414
415    #[test]
416    fn int_min_stays_integer() {
417        let number = Reader::new("-9223372036854775808".as_bytes())
418            .read_without_context::<Number>()
419            .unwrap();
420        assert_eq!(number.0, super::InternalNumber::Integer(i64::MIN));
421        assert_eq!(number.as_i64(), i64::MIN);
422    }
423
424    #[test]
425    fn real_1() {
426        assert_eq!(
427            Reader::new("3".as_bytes())
428                .read_without_context::<f32>()
429                .unwrap(),
430            3.0
431        );
432    }
433
434    #[test]
435    fn real_3() {
436        assert_eq!(
437            Reader::new("+32".as_bytes())
438                .read_without_context::<f32>()
439                .unwrap(),
440            32.0
441        );
442    }
443
444    #[test]
445    fn real_4() {
446        assert_eq!(
447            Reader::new("-32".as_bytes())
448                .read_without_context::<f32>()
449                .unwrap(),
450            -32.0
451        );
452    }
453
454    #[test]
455    fn real_5() {
456        assert_eq!(
457            Reader::new("-32.01".as_bytes())
458                .read_without_context::<f32>()
459                .unwrap(),
460            -32.01
461        );
462    }
463
464    #[test]
465    fn real_6() {
466        assert_eq!(
467            Reader::new("-.345".as_bytes())
468                .read_without_context::<f32>()
469                .unwrap(),
470            -0.345
471        );
472    }
473
474    #[test]
475    fn real_7() {
476        assert_eq!(
477            Reader::new("-.00143".as_bytes())
478                .read_without_context::<f32>()
479                .unwrap(),
480            -0.00143
481        );
482    }
483
484    #[test]
485    fn real_8() {
486        assert_eq!(
487            Reader::new("-12.0013".as_bytes())
488                .read_without_context::<f32>()
489                .unwrap(),
490            -12.0013
491        );
492    }
493
494    #[test]
495    fn real_9() {
496        assert_eq!(
497            Reader::new("98349.432534".as_bytes())
498                .read_without_context::<f32>()
499                .unwrap(),
500            98_349.43
501        );
502    }
503
504    #[test]
505    fn real_10() {
506        assert_eq!(
507            Reader::new("-34534656.34".as_bytes())
508                .read_without_context::<f32>()
509                .unwrap(),
510            -34534656.34
511        );
512    }
513
514    #[test]
515    fn real_failing() {
516        assert!(
517            Reader::new("+abc".as_bytes())
518                .read_without_context::<f32>()
519                .is_none()
520        );
521    }
522
523    #[test]
524    fn number_1() {
525        assert_eq!(
526            Reader::new("+32".as_bytes())
527                .read_without_context::<Number>()
528                .unwrap()
529                .as_f64() as f32,
530            32.0
531        );
532    }
533
534    #[test]
535    fn number_2() {
536        assert_eq!(
537            Reader::new("-32.01".as_bytes())
538                .read_without_context::<Number>()
539                .unwrap()
540                .as_f64() as f32,
541            -32.01
542        );
543    }
544
545    #[test]
546    fn number_3() {
547        assert_eq!(
548            Reader::new("-.345".as_bytes())
549                .read_without_context::<Number>()
550                .unwrap()
551                .as_f64() as f32,
552            -0.345
553        );
554    }
555
556    #[test]
557    fn large_number() {
558        assert_eq!(
559            Reader::new("38359922".as_bytes())
560                .read_without_context::<Number>()
561                .unwrap()
562                .as_i64(),
563            38359922
564        );
565    }
566
567    #[test]
568    fn large_number_2() {
569        assert_eq!(
570            Reader::new("4294966260".as_bytes())
571                .read_without_context::<u32>()
572                .unwrap(),
573            4294966260
574        );
575    }
576
577    #[test]
578    fn twenty_digit_real_does_not_wrap() {
579        let got = Reader::new("190.50000762939453225".as_bytes())
580            .read_without_context::<Number>()
581            .unwrap()
582            .as_f64();
583        assert_eq!(got, 190.500_007_629_394_53);
584    }
585
586    #[test]
587    fn nineteen_digit_real_stays_exact() {
588        let got = Reader::new("190.5000076293945313".as_bytes())
589            .read_without_context::<Number>()
590            .unwrap()
591            .as_f64();
592        assert_eq!(got, 190.500_007_629_394_53);
593    }
594
595    #[test]
596    fn oversized_integer_uses_float_fallback() {
597        let n = Reader::new("9999999999999999999".as_bytes())
598            .read_without_context::<Number>()
599            .unwrap();
600        assert_eq!(n.as_f64(), 9_999_999_999_999_999_999_u64 as f64);
601        assert_eq!(n.as_i64(), i64::MAX);
602    }
603
604    #[test]
605    fn oversized_negative_integer_uses_float_fallback() {
606        let n = Reader::new("-1234567890123456789012345".as_bytes())
607            .read_without_context::<Number>()
608            .unwrap();
609        assert_eq!(n.as_f64(), -1.234_567_890_123_456_8e24);
610        assert_eq!(n.as_i64(), i64::MIN);
611    }
612
613    #[test]
614    fn integer_overflow_then_fraction_uses_float_fallback() {
615        let got = Reader::new("12345678901234567890123.456".as_bytes())
616            .read_without_context::<Number>()
617            .unwrap()
618            .as_f64();
619        assert_eq!(got, 1.234_567_890_123_456_8e22);
620    }
621
622    #[test]
623    fn f32_max_uses_float_fallback() {
624        let got = Reader::new("340282346638528859811704183484516925440".as_bytes())
625            .read_without_context::<f32>()
626            .unwrap();
627        assert_eq!(got, f32::MAX);
628    }
629
630    #[test]
631    fn float_fallback_ignores_trailing_minus() {
632        let input = "1234567890123456789012345-123".as_bytes();
633        let mut reader = Reader::new(input);
634        let got = reader.read_without_context::<Number>().unwrap().as_f64();
635        assert_eq!(got, 1.234_567_890_123_456_8e24);
636        assert_eq!(reader.offset(), input.len());
637    }
638}