Skip to main content

hayro_postscript/
object.rs

1use crate::array::{self, Array};
2use crate::error::{Error, Result};
3use crate::name::{self, Name};
4use crate::number::{self, Number};
5use crate::reader::Reader;
6use crate::string::{self, String};
7
8/// A PostScript object.
9#[derive(Debug, Clone, PartialEq)]
10pub enum Object<'a> {
11    /// A number object.
12    Number(Number),
13    /// A name object.
14    Name(Name<'a>),
15    /// A string object.
16    String(String<'a>),
17    /// An array object.
18    Array(Array<'a>),
19}
20
21pub(crate) fn read<'a>(r: &mut Reader<'a>) -> Result<Object<'a>> {
22    skip_whitespace_and_comments(r);
23
24    let b = r.peek_byte().ok_or(Error::SyntaxError)?;
25
26    match b {
27        b'(' => string::parse_literal(r)
28            .map(|s| Object::String(String::from_literal(s)))
29            .ok_or(Error::SyntaxError),
30        b'<' => {
31            if r.peek_bytes(2) == Some(b"<~") {
32                string::parse_ascii85(r)
33                    .map(|s| Object::String(String::from_ascii85(s)))
34                    .ok_or(Error::SyntaxError)
35            } else if r.peek_bytes(2) == Some(b"<<") {
36                // TODO: Proper dict support. For now, skip `<<` and return
37                // the next object so callers see the inner tokens.
38                r.forward();
39                r.forward();
40                // TODO: This can easily overflow the stack if we have nested <<<<<.
41                read(r)
42            } else {
43                string::parse_hex(r)
44                    .map(|s| Object::String(String::from_hex(s)))
45                    .ok_or(Error::SyntaxError)
46            }
47        }
48        b'/' => name::parse_literal(r)
49            .map(|s| Object::Name(Name::new(s, true)))
50            .ok_or(Error::SyntaxError),
51        b'[' => array::parse(r).map(|d| Object::Array(Array::new(d))),
52        b'>' => {
53            if r.peek_bytes(2) == Some(b">>") {
54                // TODO: Proper dict support. Skip `>>` closing delimiter.
55                r.forward();
56                r.forward();
57                read(r)
58            } else {
59                Err(Error::SyntaxError)
60            }
61        }
62        b'{' => {
63            r.forward();
64            Err(Error::UnsupportedType)
65        }
66        b'.' | b'+' | b'-' | b'0'..=b'9' => number::read(r).map(Object::Number),
67        _ => name::parse_executable(r)
68            .map(|s| Object::Name(Name::new(s, false)))
69            .ok_or(Error::SyntaxError),
70    }
71}
72
73pub(crate) fn at_end(r: &mut Reader<'_>) -> bool {
74    skip_whitespace_and_comments(r);
75    r.peek_byte().is_none()
76}
77
78pub(crate) fn skip_whitespace_and_comments(r: &mut Reader<'_>) {
79    loop {
80        match r.peek_byte() {
81            Some(b) if crate::reader::is_whitespace(b) => {
82                r.forward();
83            }
84            Some(b'%') => {
85                r.forward();
86                r.forward_while(|b| !crate::reader::is_eol(b));
87            }
88            _ => return,
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    fn read_one(input: &[u8]) -> Result<Object<'_>> {
98        let mut r = Reader::new(input);
99        read(&mut r)
100    }
101
102    fn read_ok(input: &[u8]) -> Object<'_> {
103        read_one(input).unwrap()
104    }
105
106    fn read_err(input: &[u8]) -> Error {
107        read_one(input).unwrap_err()
108    }
109
110    #[test]
111    fn integer() {
112        assert_eq!(read_ok(b"42 "), Object::Number(Number::Integer(42)));
113    }
114
115    #[test]
116    fn negative_integer() {
117        assert_eq!(read_ok(b"-7 "), Object::Number(Number::Integer(-7)));
118    }
119
120    #[test]
121    fn literal_name() {
122        assert_eq!(
123            read_ok(b"/CMapName "),
124            Object::Name(Name::new(b"CMapName", true))
125        );
126    }
127
128    #[test]
129    fn executable_name() {
130        let obj = read_ok(b"beginbfchar ");
131        assert_eq!(obj, Object::Name(Name::new(b"beginbfchar", false)));
132    }
133
134    #[test]
135    fn literal_string() {
136        assert_eq!(
137            read_ok(b"(Hello)"),
138            Object::String(String::from_literal(b"Hello"))
139        );
140    }
141
142    #[test]
143    fn hex_string() {
144        assert_eq!(
145            read_ok(b"<48656C6C6F>"),
146            Object::String(String::from_hex(b"48656C6C6F"))
147        );
148    }
149
150    #[test]
151    fn ascii85_string() {
152        assert_eq!(
153            read_ok(b"<~87cURDZ~>"),
154            Object::String(String::from_ascii85(b"87cURDZ"))
155        );
156    }
157
158    #[test]
159    fn array_simple() {
160        let obj = read_ok(b"[1 2 3]");
161        assert_eq!(obj, Object::Array(Array::new(b"1 2 3")));
162    }
163
164    #[test]
165    fn stray_close_bracket() {
166        assert_eq!(read_err(b"]"), Error::SyntaxError);
167    }
168
169    #[test]
170    fn stray_gt() {
171        assert_eq!(read_err(b">x"), Error::SyntaxError);
172    }
173
174    #[test]
175    fn skips_whitespace() {
176        assert_eq!(read_ok(b"  \t\n 42 "), Object::Number(Number::Integer(42)));
177    }
178
179    #[test]
180    fn skips_comments() {
181        assert_eq!(
182            read_ok(b"% this is a comment\n42 "),
183            Object::Number(Number::Integer(42))
184        );
185    }
186
187    #[test]
188    fn eof() {
189        assert!(read_one(b"").is_err());
190        assert!(read_one(b"   ").is_err());
191        assert!(read_one(b"% comment only\n").is_err());
192    }
193}