color_parser/hex/
mod.rs

1use color_core::RGBA8;
2use pex::{
3    helpers::{ascii_whitespace, make_from_str},
4    ParseResult, ParseState, StopBecause,
5};
6
7pub fn hex(input: &str) -> Result<RGBA8, StopBecause> {
8    let state = ParseState::new(input.trim_end()).skip(ascii_whitespace);
9    make_from_str(state, parse_hex)
10}
11
12/// `<hex-color_parser> = #<hex-value>{3,4,6,8}`
13/// <https://www.w3.org/TR/css-color-4/#hex-notation>
14pub fn parse_hex(input: ParseState) -> ParseResult<RGBA8> {
15    let (state, _) = input.match_char('#')?;
16    let (state, hex) = state.match_str_if(|c| c.is_ascii_hexdigit(), "ASCII_HEX")?;
17    let rgba = match hex.as_bytes() {
18        [gray] => RGBA8::gray(byte2_to_u8(*gray, *gray)),
19        [gray1, gray2] => RGBA8::gray(byte2_to_u8(*gray1, *gray2)),
20        [r, g, b] => RGBA8::new(byte2_to_u8(*r, *r), byte2_to_u8(*g, *g), byte2_to_u8(*b, *b), 255),
21        [r, g, b, a] => RGBA8::new(byte2_to_u8(*r, *r), byte2_to_u8(*g, *g), byte2_to_u8(*b, *b), byte2_to_u8(*a, *a)),
22        [r1, r2, g1, g2, b1, b2] => RGBA8::new(byte2_to_u8(*r1, *r2), byte2_to_u8(*g1, *g2), byte2_to_u8(*b1, *b2), 255),
23        [r1, r2, g1, g2, b1, b2, a1, a2] =>
24            RGBA8::new(byte2_to_u8(*r1, *r2), byte2_to_u8(*g1, *g2), byte2_to_u8(*b1, *b2), byte2_to_u8(*a1, *a2)),
25        _ => StopBecause::must_be("3, 4, 6 or 8 hex digits", state.start_offset)?,
26    };
27    state.finish(rgba)
28}
29
30#[inline(always)]
31fn byte2_to_u8(high: u8, low: u8) -> u8 {
32    byte_to_u8(high) << 4 | byte_to_u8(low)
33}
34
35#[inline(always)]
36fn byte_to_u8(byte: u8) -> u8 {
37    match byte {
38        b'0'..=b'9' => byte - b'0',
39        b'a'..=b'f' => byte - b'a' + 10,
40        b'A'..=b'F' => byte - b'A' + 10,
41        _ => unreachable!(),
42    }
43}