1use std::borrow::Cow;
8
9use pdfrum_common::hex_digit;
10
11use crate::string::decode_text;
12
13#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub struct Name(Cow<'static, [u8]>);
32
33impl Name {
34 #[must_use]
36 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
37 Self(Cow::Owned(bytes.into()))
38 }
39
40 #[must_use]
43 pub const fn from_static(bytes: &'static [u8]) -> Self {
44 Self(Cow::Borrowed(bytes))
45 }
46
47 #[must_use]
50 pub fn decode(raw: &[u8]) -> Self {
51 Self(Cow::Owned(name_decode(raw)))
52 }
53
54 #[must_use]
56 pub fn as_bytes(&self) -> &[u8] {
57 &self.0
58 }
59
60 #[must_use]
62 pub fn as_str(&self) -> Option<&str> {
63 std::str::from_utf8(&self.0).ok()
64 }
65
66 #[must_use]
68 pub fn as_text(&self) -> Cow<'_, str> {
69 decode_text(&self.0)
70 }
71
72 #[must_use]
75 pub fn encode(&self) -> Vec<u8> {
76 let mut out = vec![b'/'];
77 out.extend_from_slice(&name_encode(&self.0));
78 out
79 }
80}
81
82impl std::fmt::Debug for Name {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(f, "{}", String::from_utf8_lossy(&self.encode()))
87 }
88}
89
90impl AsRef<[u8]> for Name {
91 fn as_ref(&self) -> &[u8] {
92 &self.0
93 }
94}
95
96impl From<&[u8]> for Name {
97 fn from(bytes: &[u8]) -> Self {
98 Self::new(bytes.to_vec())
99 }
100}
101
102impl From<&str> for Name {
103 fn from(s: &str) -> Self {
104 Self::new(s.as_bytes().to_vec())
105 }
106}
107
108impl From<Vec<u8>> for Name {
109 fn from(bytes: Vec<u8>) -> Self {
110 Self::new(bytes)
111 }
112}
113
114impl From<String> for Name {
115 fn from(s: String) -> Self {
116 Self::new(s.into_bytes())
117 }
118}
119
120impl From<&Name> for Name {
121 fn from(n: &Name) -> Self {
122 n.clone()
123 }
124}
125
126const fn is_pdf_whitespace(b: u8) -> bool {
131 matches!(b, 0x00 | 0x09 | 0x0A | 0x0C | 0x0D | 0x20)
132}
133
134const fn is_pdf_delimiter(b: u8) -> bool {
136 matches!(
137 b,
138 b'%' | b'(' | b')' | b'/' | b'<' | b'>' | b'[' | b']' | b'{' | b'}'
139 )
140}
141
142#[must_use]
157pub fn name_decode(raw: &[u8]) -> Vec<u8> {
158 let mut out = Vec::with_capacity(raw.len());
159 let mut i = 0;
160 while let Some(&b) = raw.get(i) {
161 if let (b'#', Some(&hi), Some(&lo)) = (b, raw.get(i + 1), raw.get(i + 2)) {
162 let digit = |b: u8| hex_digit(b).unwrap_or(0);
165 out.push(digit(hi).wrapping_mul(16).wrapping_add(digit(lo)));
166 i += 3;
167 } else {
168 out.push(b);
169 i += 1;
170 }
171 }
172 out
173}
174
175#[must_use]
189pub fn name_encode(bytes: &[u8]) -> Vec<u8> {
190 let mut out = Vec::with_capacity(bytes.len());
191 for b in bytes {
192 if *b >= 0x80 || is_pdf_whitespace(*b) || *b == b'#' || is_pdf_delimiter(*b) {
193 out.push(b'#');
194 out.extend_from_slice(&hex_pair(*b));
195 } else {
196 out.push(*b);
197 }
198 }
199 out
200}
201
202pub(crate) const fn hex_pair(b: u8) -> [u8; 2] {
204 const fn digit(nibble: u8) -> u8 {
205 match nibble {
206 0..=9 => b'0' + nibble,
207 _ => b'A' + nibble - 10,
208 }
209 }
210 [digit(b >> 4), digit(b & 0x0F)]
211}
212
213#[macro_export]
228macro_rules! names {
229 ($($(#[$meta:meta])* $konst:ident = $spelling:literal;)*) => {
230 $(
231 $(#[$meta])*
232 pub const $konst: &$crate::Name =
233 &$crate::Name::from_static($spelling.as_bytes());
234 )*
235 };
236}
237
238#[cfg(test)]
239mod tests {
240 use super::{Name, name_decode, name_encode};
241
242 #[test]
244 fn name_decode_needs_a_full_escape() {
245 assert_eq!(name_decode(b""), b"");
246 assert_eq!(name_decode(b"A"), b"A");
247 assert_eq!(name_decode(b"#"), b"#");
248 assert_eq!(name_decode(b"#4"), b"#4");
249 assert_eq!(name_decode(b"#41"), b"A");
250 assert_eq!(name_decode(b"#411"), b"A1");
251 }
252
253 #[test]
254 fn name_decode_treats_non_hex_as_zero() {
255 assert_eq!(name_decode(b"#zz9"), b"\x009");
256 assert_eq!(name_decode(b"#4z9"), b"\x409");
257 }
258
259 #[test]
261 fn name_encode_escapes_the_grammar_bytes() {
262 assert_eq!(name_encode(b""), b"");
263 assert_eq!(name_encode(b"A"), b"A");
264 assert_eq!(name_encode(b"#"), b"#23");
265 assert_eq!(name_encode(b" "), b"#20");
266 assert_eq!(
267 name_encode(b"!@#$%^&*()<>[]"),
268 b"!@#23$#25^&*#28#29#3C#3E#5B#5D"
269 );
270 assert_eq!(name_encode(b"\xc2"), b"#C2");
271 assert_eq!(name_encode(b"f\xc2\xa5"), b"f#C2#A5");
272 }
273
274 #[test]
275 fn spellings_unify_after_decoding() {
276 assert_eq!(Name::decode(b"A#42"), Name::decode(b"AB"));
277 assert_eq!(Name::decode(b"Lengt#68").as_str(), Some("Length"));
278 }
279
280 #[test]
281 fn static_and_owned_names_compare_equal() {
282 assert_eq!(Name::from_static(b"Type"), Name::from("Type"));
283 assert_eq!(Name::from(b"Type".to_vec()), Name::from("Type"));
284 assert_eq!(Name::from(String::from("Type")), Name::from("Type"));
285 }
286
287 #[test]
288 fn encode_round_trips_through_decode() {
289 for name in [&b"Length"[..], b"a b", b"#", b"\xFF\x00", b"(x)"] {
290 let encoded = name_encode(name);
291 assert_eq!(name_decode(&encoded), name, "round trip of {name:?}");
292 }
293 }
294
295 #[test]
296 fn name_encode_includes_the_slash() {
297 assert_eq!(Name::from("Length").encode(), b"/Length");
298 assert_eq!(Name::from("a b").encode(), b"/a#20b");
299 }
300}