use crate::attr::Attr;
use crate::color::Color;
use std::collections::HashMap;
const CP_MASK: u64 = (1 << 21) - 1;
const ATTR_SHIFT: u64 = 21;
const ATTR_MASK: u64 = 0xFF << ATTR_SHIFT;
const FG_SHIFT: u64 = 29;
const FG_MASK: u64 = 0x7FF << FG_SHIFT;
const BG_SHIFT: u64 = 40;
const BG_MASK: u64 = 0x7FF << BG_SHIFT;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cell(u64);
impl Default for Cell {
#[inline]
fn default() -> Self {
Self::blank()
}
}
impl Cell {
pub const BLANK: Cell = Cell(b' ' as u64);
#[inline]
pub fn blank() -> Self {
Self::BLANK
}
#[inline]
pub fn pack(ch: char, attr: Attr, fg_id: u16, bg_id: u16) -> Self {
let cp = ch as u32 as u64;
let a = (attr.bits() as u64) << ATTR_SHIFT;
let f = (fg_id as u64) << FG_SHIFT;
let b = (bg_id as u64) << BG_SHIFT;
Cell(cp | a | f | b)
}
#[inline]
pub fn ch(self) -> char {
let cp = (self.0 & CP_MASK) as u32;
char::from_u32(cp).unwrap_or('\0')
}
#[inline]
pub fn attr(self) -> Attr {
let bits = ((self.0 & ATTR_MASK) >> ATTR_SHIFT) as u16;
Attr(bits)
}
#[inline]
pub fn fg_id(self) -> u16 {
((self.0 & FG_MASK) >> FG_SHIFT) as u16
}
#[inline]
pub fn bg_id(self) -> u16 {
((self.0 & BG_MASK) >> BG_SHIFT) as u16
}
#[inline]
pub fn is_blank(self) -> bool {
self.0 == Self::BLANK.0
}
#[inline]
pub fn raw(self) -> u64 {
self.0
}
}
pub struct ColorTable {
rgb_to_id: HashMap<(u8, u8, u8), u16>,
id_to_rgb: Vec<(u8, u8, u8)>,
}
const RGB_BASE: u16 = 273;
impl ColorTable {
pub fn new() -> Self {
Self {
rgb_to_id: HashMap::new(),
id_to_rgb: Vec::new(),
}
}
#[inline]
pub fn color_to_id(&mut self, color: Color) -> u16 {
use crate::color::NamedColor::*;
match color {
Color::Named(c) => match c {
Black => 1,
Red => 2,
Green => 3,
Yellow => 4,
Blue => 5,
Magenta => 6,
Cyan => 7,
White => 8,
LightBlack => 9,
LightRed => 10,
LightGreen => 11,
LightYellow => 12,
LightBlue => 13,
LightMagenta => 14,
LightCyan => 15,
LightWhite => 16,
Foreground | Background | Cursor | LightForeground | DimForeground => 0,
DimBlack => 1,
DimRed => 2,
DimGreen => 3,
DimYellow => 4,
DimBlue => 5,
DimMagenta => 6,
DimCyan => 7,
DimWhite => 8,
},
Color::Indexed(idx) => 17 + idx as u16,
Color::Spec(rgb) => {
if let Some(&id) = self.rgb_to_id.get(&(rgb.r, rgb.g, rgb.b)) {
id
} else {
let id = RGB_BASE + self.id_to_rgb.len() as u16;
self.rgb_to_id.insert((rgb.r, rgb.g, rgb.b), id);
self.id_to_rgb.push((rgb.r, rgb.g, rgb.b));
id
}
}
}
}
#[inline]
pub fn id_to_color(&self, id: u16) -> Color {
use crate::color::NamedColor::*;
match id {
0 => Color::Named(Foreground),
1 => Color::Named(Black),
2 => Color::Named(Red),
3 => Color::Named(Green),
4 => Color::Named(Yellow),
5 => Color::Named(Blue),
6 => Color::Named(Magenta),
7 => Color::Named(Cyan),
8 => Color::Named(White),
9 => Color::Named(LightBlack),
10 => Color::Named(LightRed),
11 => Color::Named(LightGreen),
12 => Color::Named(LightYellow),
13 => Color::Named(LightBlue),
14 => Color::Named(LightMagenta),
15 => Color::Named(LightCyan),
16 => Color::Named(LightWhite),
17..=272 => Color::Indexed((id - 17) as u8),
_ => {
let idx = (id - RGB_BASE) as usize;
let (r, g, b) = self.id_to_rgb[idx];
Color::rgb(r, g, b)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cell_is_eight_bytes() {
assert_eq!(std::mem::size_of::<Cell>(), 8);
}
#[test]
fn codepoint_round_trip() {
let c = Cell::pack('🦀', Attr::NORMAL, 0, 0);
assert_eq!(c.ch(), '🦀');
let c = Cell::pack('a', Attr::NORMAL, 0, 0);
assert_eq!(c.ch(), 'a');
}
#[test]
fn attr_round_trip() {
let c = Cell::pack('x', Attr::BOLD | Attr::ITALIC, 0, 0);
assert!(c.attr().contains(Attr::BOLD));
assert!(c.attr().contains(Attr::ITALIC));
assert!(!c.attr().contains(Attr::UNDERLINE));
}
#[test]
fn color_id_round_trip() {
let c = Cell::pack('x', Attr::NORMAL, 42, 99);
assert_eq!(c.fg_id(), 42);
assert_eq!(c.bg_id(), 99);
}
#[test]
fn fields_are_independent() {
let c = Cell::pack('Z', Attr::BOLD, 100, 200);
assert_eq!(c.ch(), 'Z');
assert!(c.attr().contains(Attr::BOLD));
assert_eq!(c.fg_id(), 100);
assert_eq!(c.bg_id(), 200);
}
#[test]
fn blank_is_space() {
let b = Cell::blank();
assert_eq!(b.ch(), ' ');
assert!(b.is_blank());
}
#[test]
fn color_table_named() {
let mut ct = ColorTable::new();
assert_eq!(ct.color_to_id(Color::RESET), 0);
assert_eq!(ct.color_to_id(Color::RED), 2);
assert_eq!(ct.id_to_color(2), Color::RED);
}
#[test]
fn color_table_ansi256() {
let mut ct = ColorTable::new();
assert_eq!(ct.color_to_id(Color::indexed(42)), 59);
assert_eq!(ct.id_to_color(59), Color::indexed(42));
}
#[test]
fn color_table_rgb() {
let mut ct = ColorTable::new();
let id = ct.color_to_id(Color::rgb(40, 42, 54));
assert_eq!(id, RGB_BASE);
assert_eq!(ct.id_to_color(id), Color::rgb(40, 42, 54));
assert_eq!(ct.color_to_id(Color::rgb(40, 42, 54)), RGB_BASE);
let id2 = ct.color_to_id(Color::rgb(255, 0, 0));
assert_eq!(id2, RGB_BASE + 1);
}
#[test]
fn memory_efficiency() {
let line: Vec<Cell> = (0..80).map(|_| Cell::blank()).collect();
assert_eq!(std::mem::size_of_val(&line[..]), 640); }
}