Skip to main content

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}