hayro_syntax/object/
number.rs

1//! Number objects.
2
3use crate::object::macros::object;
4use crate::object::{Object, ObjectLike};
5use crate::reader::{Readable, Reader, Skippable};
6use crate::xref::XRef;
7use log::debug;
8use std::fmt::Debug;
9use std::str::FromStr;
10
11/// A PDF number.
12#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct Number(pub(crate) InternalNumber);
14
15impl Number {
16    /// Returns the number as a f64.
17    pub fn as_f64(&self) -> f64 {
18        match self.0 {
19            InternalNumber::Real(r) => r as f64,
20            InternalNumber::Integer(i) => i as f64,
21        }
22    }
23
24    /// Returns the number as a f32.
25    pub fn as_f32(&self) -> f32 {
26        match self.0 {
27            InternalNumber::Real(r) => r,
28            InternalNumber::Integer(i) => {
29                let converted = i as f32;
30
31                // Double check whether conversion didn't overflow.
32                if converted as i32 != i {
33                    debug!("integer {} was truncated to {}", i, converted);
34                }
35
36                converted
37            }
38        }
39    }
40
41    /// Returns the number as an i32.
42    pub fn as_i32(&self) -> i32 {
43        match self.0 {
44            InternalNumber::Real(r) => {
45                let res = r as i32;
46
47                if !(r.trunc() == r) {
48                    debug!("float {} was truncated to {}", r, res);
49                }
50
51                res
52            }
53            InternalNumber::Integer(i) => i,
54        }
55    }
56
57    /// Create a new `Number` from a f32 number.
58    pub fn from_f32(num: f32) -> Self {
59        Self(InternalNumber::Real(num))
60    }
61
62    /// Create a new `Number` from a i32 number.
63    pub fn from_i32(num: i32) -> Self {
64        Self(InternalNumber::Integer(num))
65    }
66}
67
68impl Skippable for Number {
69    fn skip<const PLAIN: bool>(r: &mut Reader<'_>) -> Option<()> {
70        r.forward_if(|b| b == b'+' || b == b'-');
71
72        match r.peek_byte()? {
73            b'.' => {
74                r.read_byte()?;
75                r.forward_while_1(is_digit)?;
76            }
77            (b'0'..=b'9') => {
78                r.forward_while_1(is_digit)?;
79                if let Some(()) = r.forward_tag(b".") {
80                    r.forward_while(is_digit);
81                }
82            }
83            _ => return None,
84        }
85
86        Some(())
87    }
88}
89
90impl Readable<'_> for Number {
91    fn read<const PLAIN: bool>(r: &mut Reader<'_>, _: &XRef) -> Option<Self> {
92        // TODO: This function is probably the biggest bottleneck in content parsing, so
93        // worth optimizing (i.e. reading the number directly from the bytes instead
94        // of first parsing it to a number).
95
96        let data = r.skip::<PLAIN, Number>()?;
97        let num = f32::from_str(std::str::from_utf8(data).ok()?).ok()?;
98
99        if num.fract() == 0.0 {
100            Some(Number(InternalNumber::Integer(num as i32)))
101        } else {
102            Some(Number(InternalNumber::Real(num)))
103        }
104    }
105}
106
107object!(Number, Number);
108
109#[derive(Clone, Copy, Debug, PartialEq)]
110pub(crate) enum InternalNumber {
111    Real(f32),
112    Integer(i32),
113}
114
115macro_rules! int_num {
116    ($i:ident) => {
117        impl Skippable for $i {
118            fn skip<const PLAIN: bool>(r: &mut Reader<'_>) -> Option<()> {
119                r.forward_if(|b| b == b'+' || b == b'-');
120                r.forward_while_1(is_digit)?;
121
122                // We have a float instead of an integer.
123                if r.peek_byte() == Some(b'.') {
124                    return None;
125                }
126
127                Some(())
128            }
129        }
130
131        impl<'a> Readable<'a> for $i {
132            fn read<const PLAIN: bool>(r: &mut Reader<'a>, xref: &'a XRef) -> Option<$i> {
133                r.read::<PLAIN, Number>(xref)
134                    .map(|n| n.as_i32())
135                    .and_then(|n| n.try_into().ok())
136            }
137        }
138
139        impl TryFrom<Object<'_>> for $i {
140            type Error = ();
141
142            fn try_from(value: Object<'_>) -> std::result::Result<Self, Self::Error> {
143                match value {
144                    Object::Number(n) => n.as_i32().try_into().ok().ok_or(()),
145                    _ => Err(()),
146                }
147            }
148        }
149
150        impl<'a> ObjectLike<'a> for $i {}
151    };
152}
153
154int_num!(i32);
155int_num!(u32);
156int_num!(u16);
157int_num!(usize);
158int_num!(u8);
159
160impl Skippable for f32 {
161    fn skip<const PLAIN: bool>(r: &mut Reader<'_>) -> Option<()> {
162        r.skip::<PLAIN, Number>().map(|_| {})
163    }
164}
165
166impl Readable<'_> for f32 {
167    fn read<const PLAIN: bool>(r: &mut Reader, _: &XRef) -> Option<Self> {
168        r.read_without_xref::<Number>().map(|n| n.as_f32())
169    }
170}
171
172impl TryFrom<Object<'_>> for f32 {
173    type Error = ();
174
175    fn try_from(value: Object<'_>) -> Result<Self, Self::Error> {
176        match value {
177            Object::Number(n) => Ok(n.as_f32()),
178            _ => Err(()),
179        }
180    }
181}
182
183impl ObjectLike<'_> for f32 {}
184
185impl Skippable for f64 {
186    fn skip<const PLAIN: bool>(r: &mut Reader<'_>) -> Option<()> {
187        r.skip::<PLAIN, Number>().map(|_| {})
188    }
189}
190
191impl Readable<'_> for f64 {
192    fn read<const PLAIN: bool>(r: &mut Reader, _: &XRef) -> Option<Self> {
193        r.read_without_xref::<Number>().map(|n| n.as_f64())
194    }
195}
196
197impl TryFrom<Object<'_>> for f64 {
198    type Error = ();
199
200    fn try_from(value: Object<'_>) -> Result<Self, Self::Error> {
201        match value {
202            Object::Number(n) => Ok(n.as_f64()),
203            _ => Err(()),
204        }
205    }
206}
207
208impl ObjectLike<'_> for f64 {}
209
210pub(crate) fn is_digit(byte: u8) -> bool {
211    byte >= b'0' && byte <= b'9'
212}
213
214#[cfg(test)]
215mod tests {
216    use crate::object::number::Number;
217    use crate::reader::Reader;
218
219    #[test]
220    fn int_1() {
221        assert_eq!(
222            Reader::new("0".as_bytes())
223                .read_without_xref::<i32>()
224                .unwrap(),
225            0
226        );
227    }
228
229    #[test]
230    fn int_3() {
231        assert_eq!(
232            Reader::new("+32".as_bytes())
233                .read_without_xref::<i32>()
234                .unwrap(),
235            32
236        );
237    }
238
239    #[test]
240    fn int_4() {
241        assert_eq!(
242            Reader::new("-32".as_bytes())
243                .read_without_xref::<i32>()
244                .unwrap(),
245            -32
246        );
247    }
248
249    #[test]
250    fn int_6() {
251        assert_eq!(
252            Reader::new("98349".as_bytes())
253                .read_without_xref::<i32>()
254                .unwrap(),
255            98349
256        );
257    }
258
259    #[test]
260    fn int_7() {
261        assert_eq!(
262            Reader::new("003245".as_bytes())
263                .read_without_xref::<i32>()
264                .unwrap(),
265            3245
266        );
267    }
268
269    #[test]
270    fn int_trailing() {
271        assert_eq!(
272            Reader::new("0abc".as_bytes())
273                .read_without_xref::<i32>()
274                .unwrap(),
275            0
276        );
277    }
278
279    #[test]
280    fn real_1() {
281        assert_eq!(
282            Reader::new("3".as_bytes())
283                .read_without_xref::<f32>()
284                .unwrap(),
285            3.0
286        );
287    }
288
289    #[test]
290    fn real_3() {
291        assert_eq!(
292            Reader::new("+32".as_bytes())
293                .read_without_xref::<f32>()
294                .unwrap(),
295            32.0
296        );
297    }
298
299    #[test]
300    fn real_4() {
301        assert_eq!(
302            Reader::new("-32".as_bytes())
303                .read_without_xref::<f32>()
304                .unwrap(),
305            -32.0
306        );
307    }
308
309    #[test]
310    fn real_5() {
311        assert_eq!(
312            Reader::new("-32.01".as_bytes())
313                .read_without_xref::<f32>()
314                .unwrap(),
315            -32.01
316        );
317    }
318
319    #[test]
320    fn real_6() {
321        assert_eq!(
322            Reader::new("-.345".as_bytes())
323                .read_without_xref::<f32>()
324                .unwrap(),
325            -0.345
326        );
327    }
328
329    #[test]
330    fn real_7() {
331        assert_eq!(
332            Reader::new("-.00143".as_bytes())
333                .read_without_xref::<f32>()
334                .unwrap(),
335            -0.00143
336        );
337    }
338
339    #[test]
340    fn real_8() {
341        assert_eq!(
342            Reader::new("-12.0013".as_bytes())
343                .read_without_xref::<f32>()
344                .unwrap(),
345            -12.0013
346        );
347    }
348
349    #[test]
350    fn real_9() {
351        assert_eq!(
352            Reader::new("98349.432534".as_bytes())
353                .read_without_xref::<f32>()
354                .unwrap(),
355            98349.432534
356        );
357    }
358
359    #[test]
360    fn real_10() {
361        assert_eq!(
362            Reader::new("-34534656.34".as_bytes())
363                .read_without_xref::<f32>()
364                .unwrap(),
365            -34534656.34
366        );
367    }
368
369    #[test]
370    fn real_trailing() {
371        assert_eq!(
372            Reader::new("0abc".as_bytes())
373                .read_without_xref::<f32>()
374                .unwrap(),
375            0.0
376        );
377    }
378
379    #[test]
380    fn real_failing() {
381        assert_eq!(
382            Reader::new("+abc".as_bytes())
383                .read_without_xref::<f32>()
384                .is_none(),
385            true
386        );
387    }
388
389    #[test]
390    fn number_1() {
391        assert_eq!(
392            Reader::new("+32".as_bytes())
393                .read_without_xref::<Number>()
394                .unwrap()
395                .as_f64() as f32,
396            32.0
397        );
398    }
399
400    #[test]
401    fn number_2() {
402        assert_eq!(
403            Reader::new("-32.01".as_bytes())
404                .read_without_xref::<Number>()
405                .unwrap()
406                .as_f64() as f32,
407            -32.01
408        );
409    }
410
411    #[test]
412    fn number_3() {
413        assert_eq!(
414            Reader::new("-.345".as_bytes())
415                .read_without_xref::<Number>()
416                .unwrap()
417                .as_f64() as f32,
418            -0.345
419        );
420    }
421}