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, Display, 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            // Before PDF 1.2, `#` was a regular character in names.
99            if b == b'#'
100                && let Some(hex) = r.peek_bytes(2)
101                && let (Some(high), Some(low)) =
102                    (decode_hex_digit(hex[0]), decode_hex_digit(hex[1]))
103            {
104                r.skip_bytes(2)?;
105                result.push(high << 4 | low);
106            } else {
107                result.push(b);
108            }
109        }
110
111        Some(Self(NameInner::Owned(result)))
112    }
113
114    /// Return a string representation of the name.
115    ///
116    /// Returns a placeholder in case the name is not UTF-8 encoded.
117    pub fn as_str(&self) -> &str {
118        core::str::from_utf8(self.as_ref()).unwrap_or("{non-ascii key}")
119    }
120}
121
122impl Debug for Name<'_> {
123    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
124        match core::str::from_utf8(self.as_ref()) {
125            Ok(s) => <str as Debug>::fmt(s, f),
126            Err(_) => <[u8] as Debug>::fmt(self.as_ref(), f),
127        }
128    }
129}
130
131impl Display for Name<'_> {
132    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
133        write!(f, "/{}", self.as_str())
134    }
135}
136
137object!(Name<'a>, Name);
138
139impl Skippable for Name<'_> {
140    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
141        skip_name_like(r, true).map(|_| ())
142    }
143}
144
145impl<'a> Readable<'a> for Name<'a> {
146    fn read(r: &mut Reader<'a>, _: &ReaderContext<'a>) -> Option<Self> {
147        let start = r.offset();
148        skip_name_like(r, true)?;
149        let end = r.offset();
150
151        // Exclude leading solidus.
152        let data = r.range(start + 1..end)?;
153        Self::new(data)
154    }
155}
156
157// This method is shared by `Name` and the parser for content stream operators (which behave like
158// names, except that they aren't preceded by a solidus.
159pub(crate) fn skip_name_like(r: &mut Reader<'_>, solidus: bool) -> Option<()> {
160    // Note that we are not validating hex escape sequences here
161    // (since this method can lie on the hot path), so it's possible
162    // this method will yield invalid names. Validation needs to happen during actual
163    // actual reading.
164    if solidus {
165        r.forward_tag(b"/")?;
166        r.forward_while(is_regular_character);
167    } else {
168        r.forward_while_1(is_regular_character)?;
169    }
170
171    Some(())
172}
173
174#[cfg(test)]
175mod tests {
176    use crate::object::Name;
177    use crate::reader::Reader;
178    use crate::reader::ReaderExt;
179    use std::ops::Deref;
180
181    #[test]
182    fn display() {
183        let name = Reader::new(b"/Test")
184            .read_without_context::<Name<'_>>()
185            .unwrap();
186
187        assert_eq!(format!("{name}"), "/Test");
188    }
189
190    #[test]
191    fn name_1() {
192        assert_eq!(
193            Reader::new("/".as_bytes())
194                .read_without_context::<Name<'_>>()
195                .unwrap()
196                .deref(),
197            b""
198        );
199    }
200
201    #[test]
202    fn name_2() {
203        assert!(
204            Reader::new("dfg".as_bytes())
205                .read_without_context::<Name<'_>>()
206                .is_none()
207        );
208    }
209
210    #[test]
211    fn name_3() {
212        assert_eq!(
213            Reader::new("/AB#FG".as_bytes())
214                .read_without_context::<Name<'_>>()
215                .unwrap()
216                .deref(),
217            b"AB#FG"
218        );
219    }
220
221    #[test]
222    fn name_4() {
223        assert_eq!(
224            Reader::new("/Name1".as_bytes())
225                .read_without_context::<Name<'_>>()
226                .unwrap()
227                .deref(),
228            b"Name1"
229        );
230    }
231
232    #[test]
233    fn name_5() {
234        assert_eq!(
235            Reader::new("/ASomewhatLongerName".as_bytes())
236                .read_without_context::<Name<'_>>()
237                .unwrap()
238                .deref(),
239            b"ASomewhatLongerName"
240        );
241    }
242
243    #[test]
244    fn name_6() {
245        assert_eq!(
246            Reader::new("/A;Name_With-Various***Characters?".as_bytes())
247                .read_without_context::<Name<'_>>()
248                .unwrap()
249                .deref(),
250            b"A;Name_With-Various***Characters?"
251        );
252    }
253
254    #[test]
255    fn name_7() {
256        assert_eq!(
257            Reader::new("/1.2".as_bytes())
258                .read_without_context::<Name<'_>>()
259                .unwrap()
260                .deref(),
261            b"1.2"
262        );
263    }
264
265    #[test]
266    fn name_8() {
267        assert_eq!(
268            Reader::new("/$$".as_bytes())
269                .read_without_context::<Name<'_>>()
270                .unwrap()
271                .deref(),
272            b"$$"
273        );
274    }
275
276    #[test]
277    fn name_9() {
278        assert_eq!(
279            Reader::new("/@pattern".as_bytes())
280                .read_without_context::<Name<'_>>()
281                .unwrap()
282                .deref(),
283            b"@pattern"
284        );
285    }
286
287    #[test]
288    fn name_10() {
289        assert_eq!(
290            Reader::new("/.notdef".as_bytes())
291                .read_without_context::<Name<'_>>()
292                .unwrap()
293                .deref(),
294            b".notdef"
295        );
296    }
297
298    #[test]
299    fn name_11() {
300        assert_eq!(
301            Reader::new("/lime#20Green".as_bytes())
302                .read_without_context::<Name<'_>>()
303                .unwrap()
304                .deref(),
305            b"lime Green"
306        );
307    }
308
309    #[test]
310    fn name_12() {
311        assert_eq!(
312            Reader::new("/paired#28#29parentheses".as_bytes())
313                .read_without_context::<Name<'_>>()
314                .unwrap()
315                .deref(),
316            b"paired()parentheses"
317        );
318    }
319
320    #[test]
321    fn name_13() {
322        assert_eq!(
323            Reader::new("/The_Key_of_F#23_Minor".as_bytes())
324                .read_without_context::<Name<'_>>()
325                .unwrap()
326                .deref(),
327            b"The_Key_of_F#_Minor"
328        );
329    }
330
331    #[test]
332    fn name_14() {
333        assert_eq!(
334            Reader::new("/A#42".as_bytes())
335                .read_without_context::<Name<'_>>()
336                .unwrap()
337                .deref(),
338            b"AB"
339        );
340    }
341
342    #[test]
343    fn name_15() {
344        assert_eq!(
345            Reader::new("/A#3b".as_bytes())
346                .read_without_context::<Name<'_>>()
347                .unwrap()
348                .deref(),
349            b"A;"
350        );
351    }
352
353    #[test]
354    fn name_16() {
355        assert_eq!(
356            Reader::new("/A#3B".as_bytes())
357                .read_without_context::<Name<'_>>()
358                .unwrap()
359                .deref(),
360            b"A;"
361        );
362    }
363
364    #[test]
365    fn name_17() {
366        assert_eq!(
367            Reader::new("/k1  ".as_bytes())
368                .read_without_context::<Name<'_>>()
369                .unwrap()
370                .deref(),
371            b"k1"
372        );
373    }
374
375    #[test]
376    fn name_18() {
377        assert_eq!(
378            Reader::new("/#".as_bytes())
379                .read_without_context::<Name<'_>>()
380                .unwrap()
381                .deref(),
382            b"#"
383        );
384    }
385
386    #[test]
387    fn name_19() {
388        assert_eq!(
389            Reader::new("/A#4".as_bytes())
390                .read_without_context::<Name<'_>>()
391                .unwrap()
392                .deref(),
393            b"A#4"
394        );
395    }
396
397    #[test]
398    fn name_20() {
399        assert_eq!(
400            Reader::new("/#2#23".as_bytes())
401                .read_without_context::<Name<'_>>()
402                .unwrap()
403                .deref(),
404            b"#2#"
405        );
406    }
407}