pub(crate) const PALETTE_SIZE: usize = 256;
const BASE16_COLORS: usize = 16;
const COLOR_CUBE_SIZE: usize = 216;
const COLOR_CUBE_LEVELS: usize = 6;
const COLOR_CUBE_START: usize = BASE16_COLORS;
const COLOR_CUBE_BASE: usize = 55;
const COLOR_CUBE_STEP: usize = 40;
const GRAYSCALE_RAMP_SIZE: usize = 24;
const GRAYSCALE_START: usize = COLOR_CUBE_START + COLOR_CUBE_SIZE;
const GRAYSCALE_BASE: usize = 8;
const GRAYSCALE_STEP: usize = 10;
const BASE16: [(u8, u8, u8); BASE16_COLORS] = [
(0, 0, 0), (205, 0, 0), (0, 205, 0), (205, 205, 0), (0, 0, 238), (205, 0, 205), (0, 205, 205), (229, 229, 229), (127, 127, 127), (255, 0, 0), (0, 255, 0), (255, 255, 0), (92, 92, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255), ];
pub(crate) fn default_xterm_palette() -> Box<[(u8, u8, u8); PALETTE_SIZE]> {
let mut p = [(0u8, 0u8, 0u8); PALETTE_SIZE];
p[..BASE16_COLORS].copy_from_slice(&BASE16);
for i in 0..COLOR_CUBE_SIZE {
let r = i / (COLOR_CUBE_LEVELS * COLOR_CUBE_LEVELS);
let g = (i / COLOR_CUBE_LEVELS) % COLOR_CUBE_LEVELS;
let b = i % COLOR_CUBE_LEVELS;
let to_val = |v: usize| {
if v == 0 {
0u8
} else {
(COLOR_CUBE_BASE + COLOR_CUBE_STEP * v) as u8
}
};
p[COLOR_CUBE_START + i] = (to_val(r), to_val(g), to_val(b));
}
for i in 0..GRAYSCALE_RAMP_SIZE {
let v = (GRAYSCALE_BASE + GRAYSCALE_STEP * i) as u8;
p[GRAYSCALE_START + i] = (v, v, v);
}
Box::new(p)
}
pub(crate) fn default_xterm_color(idx: usize) -> (u8, u8, u8) {
if idx < 16 {
BASE16[idx]
} else if idx < 232 {
let i = idx - 16;
let r = i / 36;
let g = (i / 6) % 6;
let b = i % 6;
let to_val = |v: usize| -> u8 { if v == 0 { 0 } else { (55 + 40 * v) as u8 } };
(to_val(r), to_val(g), to_val(b))
} else {
let v = (8 + 10 * (idx - 232)) as u8;
(v, v, v)
}
}
pub(crate) fn parse_x11_color(bytes: &[u8]) -> Option<(u8, u8, u8)> {
let s = std::str::from_utf8(bytes).ok()?;
if let Some(rest) = s.strip_prefix("rgb:") {
let parts: Vec<&str> = rest.split('/').collect();
if parts.len() != 3 {
return None;
}
let scale = |v: u16, len: usize| -> u8 {
match len {
1 => (v * 17) as u8,
2 => v as u8,
3 => (v >> 4) as u8,
4 => (v >> 8) as u8,
_ => v as u8,
}
};
let r = u16::from_str_radix(parts[0], 16).ok()?;
let g = u16::from_str_radix(parts[1], 16).ok()?;
let b = u16::from_str_radix(parts[2], 16).ok()?;
Some((
scale(r, parts[0].len()),
scale(g, parts[1].len()),
scale(b, parts[2].len()),
))
} else if let Some(hex) = s.strip_prefix('#') {
if hex.len() == 6 {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some((r, g, b))
} else {
None
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_x11_color_rgb_2digit() {
assert_eq!(parse_x11_color(b"rgb:ff/00/80"), Some((255, 0, 128)));
}
#[test]
fn parse_x11_color_rgb_4digit() {
assert_eq!(parse_x11_color(b"rgb:ffff/0000/8080"), Some((255, 0, 128)));
}
#[test]
fn parse_x11_color_hex() {
assert_eq!(parse_x11_color(b"#ff0080"), Some((255, 0, 128)));
}
#[test]
fn parse_x11_color_rgb_1digit() {
assert_eq!(parse_x11_color(b"rgb:f/0/8"), Some((255, 0, 136)));
}
#[test]
fn parse_x11_color_invalid() {
assert_eq!(parse_x11_color(b"not a color"), None);
assert_eq!(parse_x11_color(b"#fff"), None);
}
}