1use zpdf_core::{Error, Result};
2
3#[derive(Debug, Clone, Copy)]
4pub struct PdfHeader {
5 pub major: u8,
6 pub minor: u8,
7}
8
9pub fn parse_header(data: &[u8]) -> Result<PdfHeader> {
10 let prefix = b"%PDF-";
11 let pos = data
12 .windows(prefix.len())
13 .position(|w| w == prefix)
14 .ok_or(Error::NotAPdf)?;
15
16 let rest = &data[pos + prefix.len()..];
17 if rest.len() < 3 {
18 return Err(Error::NotAPdf);
19 }
20
21 let major = rest[0]
22 .checked_sub(b'0')
23 .filter(|&v| v <= 9)
24 .ok_or(Error::NotAPdf)?;
25
26 if rest[1] != b'.' {
27 return Err(Error::NotAPdf);
28 }
29
30 let minor = rest[2]
31 .checked_sub(b'0')
32 .filter(|&v| v <= 9)
33 .ok_or(Error::NotAPdf)?;
34
35 Ok(PdfHeader { major, minor })
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn valid_header() {
44 let data = b"%PDF-1.7\n";
45 let h = parse_header(data).unwrap();
46 assert_eq!(h.major, 1);
47 assert_eq!(h.minor, 7);
48 }
49
50 #[test]
51 fn pdf_2_0() {
52 let data = b"%PDF-2.0\n";
53 let h = parse_header(data).unwrap();
54 assert_eq!(h.major, 2);
55 assert_eq!(h.minor, 0);
56 }
57
58 #[test]
59 fn garbage_before_header() {
60 let data = b"\xef\xbb\xbf%PDF-1.4\n";
61 let h = parse_header(data).unwrap();
62 assert_eq!(h.major, 1);
63 assert_eq!(h.minor, 4);
64 }
65
66 #[test]
67 fn not_a_pdf() {
68 assert!(parse_header(b"not a pdf").is_err());
69 }
70}