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            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
126impl Display for Name<'_> {
127    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
128        write!(f, "/{}", self.as_str())
129    }
130}
131
132object!(Name<'a>, Name);
133
134impl Skippable for Name<'_> {
135    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
136        skip_name_like(r, true).map(|_| ())
137    }
138}
139
140impl<'a> Readable<'a> for Name<'a> {
141    fn read(r: &mut Reader<'a>, _: &ReaderContext<'a>) -> Option<Self> {
142        let start = r.offset();
143        skip_name_like(r, true)?;
144        let end = r.offset();
145
146        // Exclude leading solidus.
147        let data = r.range(start + 1..end)?;
148        Self::new(data)
149    }
150}
151
152// This method is shared by `Name` and the parser for content stream operators (which behave like
153// names, except that they aren't preceded by a solidus.
154pub(crate) fn skip_name_like(r: &mut Reader<'_>, solidus: bool) -> Option<()> {
155    // Note that we are not validating hex escape sequences here
156    // (since this method can lie on the hot path), so it's possible
157    // this method will yield invalid names. Validation needs to happen during actual
158    // actual reading.
159    if solidus {
160        r.forward_tag(b"/")?;
161        r.forward_while(is_regular_character);
162    } else {
163        r.forward_while_1(is_regular_character)?;
164    }
165
166    Some(())
167}
168
169#[cfg(test)]
170mod tests {
171    use crate::object::Name;
172    use crate::reader::Reader;
173    use crate::reader::ReaderExt;
174    use std::ops::Deref;
175
176    #[test]
177    fn display() {
178        let name = Reader::new(b"/Test")
179            .read_without_context::<Name<'_>>()
180            .unwrap();
181
182        assert_eq!(format!("{name}"), "/Test");
183    }
184
185    #[test]
186    fn name_1() {
187        assert_eq!(
188            Reader::new("/".as_bytes())
189                .read_without_context::<Name<'_>>()
190                .unwrap()
191                .deref(),
192            b""
193        );
194    }
195
196    #[test]
197    fn name_2() {
198        assert!(
199            Reader::new("dfg".as_bytes())
200                .read_without_context::<Name<'_>>()
201                .is_none()
202        );
203    }
204
205    #[test]
206    fn name_3() {
207        assert!(
208            Reader::new("/AB#FG".as_bytes())
209                .read_without_context::<Name<'_>>()
210                .is_none()
211        );
212    }
213
214    #[test]
215    fn name_4() {
216        assert_eq!(
217            Reader::new("/Name1".as_bytes())
218                .read_without_context::<Name<'_>>()
219                .unwrap()
220                .deref(),
221            b"Name1"
222        );
223    }
224
225    #[test]
226    fn name_5() {
227        assert_eq!(
228            Reader::new("/ASomewhatLongerName".as_bytes())
229                .read_without_context::<Name<'_>>()
230                .unwrap()
231                .deref(),
232            b"ASomewhatLongerName"
233        );
234    }
235
236    #[test]
237    fn name_6() {
238        assert_eq!(
239            Reader::new("/A;Name_With-Various***Characters?".as_bytes())
240                .read_without_context::<Name<'_>>()
241                .unwrap()
242                .deref(),
243            b"A;Name_With-Various***Characters?"
244        );
245    }
246
247    #[test]
248    fn name_7() {
249        assert_eq!(
250            Reader::new("/1.2".as_bytes())
251                .read_without_context::<Name<'_>>()
252                .unwrap()
253                .deref(),
254            b"1.2"
255        );
256    }
257
258    #[test]
259    fn name_8() {
260        assert_eq!(
261            Reader::new("/$$".as_bytes())
262                .read_without_context::<Name<'_>>()
263                .unwrap()
264                .deref(),
265            b"$$"
266        );
267    }
268
269    #[test]
270    fn name_9() {
271        assert_eq!(
272            Reader::new("/@pattern".as_bytes())
273                .read_without_context::<Name<'_>>()
274                .unwrap()
275                .deref(),
276            b"@pattern"
277        );
278    }
279
280    #[test]
281    fn name_10() {
282        assert_eq!(
283            Reader::new("/.notdef".as_bytes())
284                .read_without_context::<Name<'_>>()
285                .unwrap()
286                .deref(),
287            b".notdef"
288        );
289    }
290
291    #[test]
292    fn name_11() {
293        assert_eq!(
294            Reader::new("/lime#20Green".as_bytes())
295                .read_without_context::<Name<'_>>()
296                .unwrap()
297                .deref(),
298            b"lime Green"
299        );
300    }
301
302    #[test]
303    fn name_12() {
304        assert_eq!(
305            Reader::new("/paired#28#29parentheses".as_bytes())
306                .read_without_context::<Name<'_>>()
307                .unwrap()
308                .deref(),
309            b"paired()parentheses"
310        );
311    }
312
313    #[test]
314    fn name_13() {
315        assert_eq!(
316            Reader::new("/The_Key_of_F#23_Minor".as_bytes())
317                .read_without_context::<Name<'_>>()
318                .unwrap()
319                .deref(),
320            b"The_Key_of_F#_Minor"
321        );
322    }
323
324    #[test]
325    fn name_14() {
326        assert_eq!(
327            Reader::new("/A#42".as_bytes())
328                .read_without_context::<Name<'_>>()
329                .unwrap()
330                .deref(),
331            b"AB"
332        );
333    }
334
335    #[test]
336    fn name_15() {
337        assert_eq!(
338            Reader::new("/A#3b".as_bytes())
339                .read_without_context::<Name<'_>>()
340                .unwrap()
341                .deref(),
342            b"A;"
343        );
344    }
345
346    #[test]
347    fn name_16() {
348        assert_eq!(
349            Reader::new("/A#3B".as_bytes())
350                .read_without_context::<Name<'_>>()
351                .unwrap()
352                .deref(),
353            b"A;"
354        );
355    }
356
357    #[test]
358    fn name_17() {
359        assert_eq!(
360            Reader::new("/k1  ".as_bytes())
361                .read_without_context::<Name<'_>>()
362                .unwrap()
363                .deref(),
364            b"k1"
365        );
366    }
367}