Skip to main content

hayro_syntax/object/
number.rs

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