pdfrum_common/hex.rs
1//! One hexadecimal digit.
2//!
3//! Five grammars in this workspace read hex digits — `#xx` name escapes,
4//! `<…>` hex strings, `/ASCIIHexDecode` data, a Type 1 program's hex-encoded
5//! eexec portion and a ToUnicode CMap's `<…>` codes. One copy, at the bottom
6//! of the graph, is the only way to keep them from drifting.
7
8/// The value of one ASCII hexadecimal digit, either case, and `None` for any
9/// other byte.
10///
11/// A caller that wants the permissive reading — a bad digit counts as zero —
12/// spells it as `hex_digit(b).unwrap_or(0)`, so the permissiveness is visible
13/// at the one place it is meant.
14///
15/// ```
16/// use pdfrum_common::hex_digit;
17///
18/// assert_eq!(hex_digit(b'7'), Some(7));
19/// assert_eq!(hex_digit(b'a'), Some(10));
20/// assert_eq!(hex_digit(b'F'), Some(15));
21/// assert_eq!(hex_digit(b'g'), None);
22/// assert_eq!(hex_digit(b' '), None);
23/// ```
24#[must_use]
25pub const fn hex_digit(byte: u8) -> Option<u8> {
26 match byte {
27 b'0'..=b'9' => Some(byte - b'0'),
28 b'a'..=b'f' => Some(byte - b'a' + 10),
29 b'A'..=b'F' => Some(byte - b'A' + 10),
30 _ => None,
31 }
32}
33
34#[cfg(test)]
35mod tests {
36 use super::hex_digit;
37
38 #[test]
39 fn the_twenty_two_digits_and_nothing_else() {
40 let expected: Vec<(u8, u8)> = (b'0'..=b'9')
41 .zip(0..)
42 .chain((b'a'..=b'f').zip(10..))
43 .chain((b'A'..=b'F').zip(10..))
44 .collect();
45 for (byte, value) in &expected {
46 assert_eq!(hex_digit(*byte), Some(*value), "{}", char::from(*byte));
47 }
48 let hits = (0..=u8::MAX).filter(|b| hex_digit(*b).is_some()).count();
49 assert_eq!(hits, expected.len());
50 }
51}