Skip to main content

hayro_syntax/object/
name.rs

1//! Names.
2
3use crate::filter::ascii_hex::decode_hex_digit;
4use crate::object::Object;
5use crate::object::macros::object;
6use crate::reader::Reader;
7use crate::reader::{Readable, ReaderContext, Skippable};
8use crate::trivia::is_regular_character;
9use core::borrow::Borrow;
10use core::fmt::{self, Debug, Formatter};
11use core::hash::{Hash, Hasher};
12use core::ops::Deref;
13use smallvec::SmallVec;
14
15#[derive(Clone)]
16enum NameInner<'a> {
17    Borrowed(&'a [u8]),
18    Owned(SmallVec<[u8; 23]>),
19}
20
21/// A PDF name object.
22#[derive(Clone)]
23pub struct Name<'a>(NameInner<'a>);
24
25impl<'a> Deref for Name<'a> {
26    type Target = [u8];
27
28    fn deref(&self) -> &Self::Target {
29        self.as_ref()
30    }
31}
32
33impl AsRef<[u8]> for Name<'_> {
34    fn as_ref(&self) -> &[u8] {
35        match &self.0 {
36            NameInner::Borrowed(data) => data,
37            NameInner::Owned(data) => data,
38        }
39    }
40}
41
42impl Borrow<[u8]> for Name<'_> {
43    fn borrow(&self) -> &[u8] {
44        self.as_ref()
45    }
46}
47
48impl PartialEq for Name<'_> {
49    fn eq(&self, other: &Self) -> bool {
50        self.as_ref() == other.as_ref()
51    }
52}
53
54impl Eq for Name<'_> {}
55
56impl Hash for Name<'_> {
57    fn hash<H: Hasher>(&self, state: &mut H) {
58        self.as_ref().hash(state);
59    }
60}
61
62impl PartialOrd for Name<'_> {
63    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
64        Some(self.cmp(other))
65    }
66}
67
68impl Ord for Name<'_> {
69    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
70        self.as_ref().cmp(other.as_ref())
71    }
72}
73
74impl<'a> Name<'a> {
75    /// Create a new name from a sequence of bytes.
76    #[inline]
77    pub fn new(data: &'a [u8]) -> Option<Self> {
78        if !data.contains(&b'#') {
79            Some(Self::new_unescaped(data))
80        } else {
81            Self::new_escaped(data)
82        }
83    }
84
85    /// Create a new name from an unescaped byte sequence.
86    #[inline]
87    pub fn new_unescaped(data: &'a [u8]) -> Self {
88        Self(NameInner::Borrowed(data))
89    }
90
91    /// Create a new name from bytes that may contain escape sequences.
92    #[inline]
93    pub fn new_escaped(data: &'a [u8]) -> Option<Self> {
94        let mut result = SmallVec::new();
95        let mut r = Reader::new(data);
96
97        while let Some(b) = r.read_byte() {
98            if b == b'#' {
99                let hex = r.read_bytes(2)?;
100                result.push(decode_hex_digit(hex[0])? << 4 | decode_hex_digit(hex[1])?);
101            } else {
102                result.push(b);
103            }
104        }
105
106        Some(Self(NameInner::Owned(result)))
107    }
108
109    /// Return a string representation of the name.
110    ///
111    /// Returns a placeholder in case the name is not UTF-8 encoded.
112    pub fn as_str(&self) -> &str {
113        core::str::from_utf8(self.as_ref()).unwrap_or("{non-ascii key}")
114    }
115}
116
117impl Debug for Name<'_> {
118    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119        match core::str::from_utf8(self.as_ref()) {
120            Ok(s) => <str as Debug>::fmt(s, f),
121            Err(_) => <[u8] as Debug>::fmt(self.as_ref(), f),
122        }
123    }
124}
125
126object!(Name<'a>, Name);
127
128impl Skippable for Name<'_> {
129    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
130        skip_name_like(r, true).map(|_| ())
131    }
132}
133
134impl<'a> Readable<'a> for Name<'a> {
135    fn read(r: &mut Reader<'a>, _: &ReaderContext<'a>) -> Option<Self> {
136        let start = r.offset();
137        skip_name_like(r, true)?;
138        let end = r.offset();
139
140        // Exclude leading solidus.
141        let data = r.range(start + 1..end)?;
142        Self::new(data)
143    }
144}
145
146// This method is shared by `Name` and the parser for content stream operators (which behave like
147// names, except that they aren't preceded by a solidus.
148pub(crate) fn skip_name_like(r: &mut Reader<'_>, solidus: bool) -> Option<()> {
149    // Note that we are not validating hex escape sequences here
150    // (since this method can lie on the hot path), so it's possible
151    // this method will yield invalid names. Validation needs to happen during actual
152    // actual reading.
153    if solidus {
154        r.forward_tag(b"/")?;
155        r.forward_while(is_regular_character);
156    } else {
157        r.forward_while_1(is_regular_character)?;
158    }
159
160    Some(())
161}
162
163#[cfg(test)]
164mod tests {
165    use crate::object::Name;
166    use crate::reader::Reader;
167    use crate::reader::ReaderExt;
168    use std::ops::Deref;
169
170    #[test]
171    fn name_1() {
172        assert_eq!(
173            Reader::new("/".as_bytes())
174                .read_without_context::<Name<'_>>()
175                .unwrap()
176                .deref(),
177            b""
178        );
179    }
180
181    #[test]
182    fn name_2() {
183        assert!(
184            Reader::new("dfg".as_bytes())
185                .read_without_context::<Name<'_>>()
186                .is_none()
187        );
188    }
189
190    #[test]
191    fn name_3() {
192        assert!(
193            Reader::new("/AB#FG".as_bytes())
194                .read_without_context::<Name<'_>>()
195                .is_none()
196        );
197    }
198
199    #[test]
200    fn name_4() {
201        assert_eq!(
202            Reader::new("/Name1".as_bytes())
203                .read_without_context::<Name<'_>>()
204                .unwrap()
205                .deref(),
206            b"Name1"
207        );
208    }
209
210    #[test]
211    fn name_5() {
212        assert_eq!(
213            Reader::new("/ASomewhatLongerName".as_bytes())
214                .read_without_context::<Name<'_>>()
215                .unwrap()
216                .deref(),
217            b"ASomewhatLongerName"
218        );
219    }
220
221    #[test]
222    fn name_6() {
223        assert_eq!(
224            Reader::new("/A;Name_With-Various***Characters?".as_bytes())
225                .read_without_context::<Name<'_>>()
226                .unwrap()
227                .deref(),
228            b"A;Name_With-Various***Characters?"
229        );
230    }
231
232    #[test]
233    fn name_7() {
234        assert_eq!(
235            Reader::new("/1.2".as_bytes())
236                .read_without_context::<Name<'_>>()
237                .unwrap()
238                .deref(),
239            b"1.2"
240        );
241    }
242
243    #[test]
244    fn name_8() {
245        assert_eq!(
246            Reader::new("/$$".as_bytes())
247                .read_without_context::<Name<'_>>()
248                .unwrap()
249                .deref(),
250            b"$$"
251        );
252    }
253
254    #[test]
255    fn name_9() {
256        assert_eq!(
257            Reader::new("/@pattern".as_bytes())
258                .read_without_context::<Name<'_>>()
259                .unwrap()
260                .deref(),
261            b"@pattern"
262        );
263    }
264
265    #[test]
266    fn name_10() {
267        assert_eq!(
268            Reader::new("/.notdef".as_bytes())
269                .read_without_context::<Name<'_>>()
270                .unwrap()
271                .deref(),
272            b".notdef"
273        );
274    }
275
276    #[test]
277    fn name_11() {
278        assert_eq!(
279            Reader::new("/lime#20Green".as_bytes())
280                .read_without_context::<Name<'_>>()
281                .unwrap()
282                .deref(),
283            b"lime Green"
284        );
285    }
286
287    #[test]
288    fn name_12() {
289        assert_eq!(
290            Reader::new("/paired#28#29parentheses".as_bytes())
291                .read_without_context::<Name<'_>>()
292                .unwrap()
293                .deref(),
294            b"paired()parentheses"
295        );
296    }
297
298    #[test]
299    fn name_13() {
300        assert_eq!(
301            Reader::new("/The_Key_of_F#23_Minor".as_bytes())
302                .read_without_context::<Name<'_>>()
303                .unwrap()
304                .deref(),
305            b"The_Key_of_F#_Minor"
306        );
307    }
308
309    #[test]
310    fn name_14() {
311        assert_eq!(
312            Reader::new("/A#42".as_bytes())
313                .read_without_context::<Name<'_>>()
314                .unwrap()
315                .deref(),
316            b"AB"
317        );
318    }
319
320    #[test]
321    fn name_15() {
322        assert_eq!(
323            Reader::new("/A#3b".as_bytes())
324                .read_without_context::<Name<'_>>()
325                .unwrap()
326                .deref(),
327            b"A;"
328        );
329    }
330
331    #[test]
332    fn name_16() {
333        assert_eq!(
334            Reader::new("/A#3B".as_bytes())
335                .read_without_context::<Name<'_>>()
336                .unwrap()
337                .deref(),
338            b"A;"
339        );
340    }
341
342    #[test]
343    fn name_17() {
344        assert_eq!(
345            Reader::new("/k1  ".as_bytes())
346                .read_without_context::<Name<'_>>()
347                .unwrap()
348                .deref(),
349            b"k1"
350        );
351    }
352}