use crate::cell::{Cell, CellFlags};
use crate::color::Color;
use crate::cursor::CursorShape;
use crate::damage::ScrollOp;
use core::num::NonZeroU32;
use std::collections::BTreeMap;
const MAGIC: [u8; 2] = *b"JT";
const VERSION: u8 = 4;
pub const WIRE_VERSION: u8 = VERSION;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FrameKind {
Full,
Partial,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Span {
pub line: u16,
pub left: u16,
pub right: u16,
pub cells: Vec<Cell>,
pub combining: BTreeMap<usize, NonZeroU32>,
pub links: BTreeMap<usize, NonZeroU32>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Frame {
pub cols: u16,
pub rows: u16,
pub kind: FrameKind,
pub cursor_row: u16,
pub cursor_col: u16,
pub cursor_visible: bool,
pub cursor_shape: CursorShape,
pub cursor_blink: bool,
pub scroll: Option<ScrollOp>,
pub spans: Vec<Span>,
pub side_table: Vec<Vec<char>>,
pub link_table: Vec<String>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DecodeError {
Truncated,
BadMagic,
BadVersion(u8),
BadTag,
BadSpan,
}
pub fn encode(frame: &Frame) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&MAGIC);
out.push(VERSION);
out.push(frame.scroll.is_some() as u8);
out.push(match frame.kind {
FrameKind::Full => 0,
FrameKind::Partial => 1,
});
out.extend_from_slice(&frame.cols.to_le_bytes());
out.extend_from_slice(&frame.rows.to_le_bytes());
out.extend_from_slice(&frame.cursor_row.to_le_bytes());
out.extend_from_slice(&frame.cursor_col.to_le_bytes());
out.push(frame.cursor_visible as u8);
out.push(match frame.cursor_shape {
CursorShape::Block => 0,
CursorShape::Underline => 1,
CursorShape::Bar => 2,
});
out.push(frame.cursor_blink as u8);
if let Some(s) = frame.scroll {
out.extend_from_slice(&(s.top as u16).to_le_bytes());
out.extend_from_slice(&(s.bottom as u16).to_le_bytes());
out.extend_from_slice(&(s.count as i16).to_le_bytes());
}
out.extend_from_slice(&(frame.spans.len() as u16).to_le_bytes());
for span in &frame.spans {
out.extend_from_slice(&span.line.to_le_bytes());
out.extend_from_slice(&span.left.to_le_bytes());
out.extend_from_slice(&span.right.to_le_bytes());
for (col, cell) in span.cells.iter().enumerate() {
let extra = span.combining.get(&col).map_or(0, |n| n.get() as u16);
let link = span.links.get(&col).map_or(0, |n| n.get() as u16);
out.extend_from_slice(&encode_cell_record(cell, extra, link));
}
}
out.extend_from_slice(&(frame.side_table.len() as u16).to_le_bytes());
for cluster in &frame.side_table {
out.extend_from_slice(&(cluster.len() as u16).to_le_bytes());
for &ch in cluster {
out.extend_from_slice(&(ch as u32).to_le_bytes());
}
}
out.extend_from_slice(&(frame.link_table.len() as u16).to_le_bytes());
for uri in &frame.link_table {
out.extend_from_slice(&(uri.len() as u16).to_le_bytes());
out.extend_from_slice(uri.as_bytes());
}
out
}
pub const CELL_RECORD_LEN: usize = 18;
pub fn encode_cell_record(cell: &Cell, extra: u16, link: u16) -> [u8; CELL_RECORD_LEN] {
let mut r = [0u8; CELL_RECORD_LEN];
r[0..4].copy_from_slice(&(cell.c() as u32).to_le_bytes());
r[4..8].copy_from_slice(&encode_color(cell.fg()).to_le_bytes());
r[8..12].copy_from_slice(&encode_color(cell.bg()).to_le_bytes());
r[12..14].copy_from_slice(&cell.flags().bits().to_le_bytes());
r[14..16].copy_from_slice(&extra.to_le_bytes());
r[16..18].copy_from_slice(&link.to_le_bytes());
r
}
pub fn encode_color(c: Color) -> u32 {
match c {
Color::Default => 0,
Color::Indexed(i) => (1 << 24) | i as u32,
Color::Rgb(r, g, b) => (2 << 24) | (r as u32) << 16 | (g as u32) << 8 | b as u32,
}
}
pub fn decode(bytes: &[u8]) -> Result<Frame, DecodeError> {
let mut r = Reader::new(bytes);
if r.take(2)? != MAGIC {
return Err(DecodeError::BadMagic);
}
let version = r.u8()?;
if version != VERSION {
return Err(DecodeError::BadVersion(version));
}
let has_scroll = r.u8()? != 0;
let kind = match r.u8()? {
0 => FrameKind::Full,
1 => FrameKind::Partial,
_ => return Err(DecodeError::BadTag),
};
let cols = r.u16()?;
let rows = r.u16()?;
let cursor_row = r.u16()?;
let cursor_col = r.u16()?;
let cursor_visible = r.u8()? != 0;
let cursor_shape = match r.u8()? {
0 => CursorShape::Block,
1 => CursorShape::Underline,
2 => CursorShape::Bar,
_ => return Err(DecodeError::BadTag),
};
let cursor_blink = r.u8()? != 0;
let scroll = if has_scroll {
let top = r.u16()? as usize;
let bottom = r.u16()? as usize;
let count = (r.u16()? as i16) as isize;
Some(ScrollOp { top, bottom, count })
} else {
None
};
let span_count = r.u16()?;
let mut spans = Vec::with_capacity(span_count as usize);
for _ in 0..span_count {
let line = r.u16()?;
let left = r.u16()?;
let right = r.u16()?;
if right < left {
return Err(DecodeError::BadSpan);
}
let n = right as usize - left as usize + 1;
let mut cells = Vec::with_capacity(n);
let mut combining = BTreeMap::new();
let mut links = BTreeMap::new();
for col in 0..n {
let (cell, extra, link) = decode_cell(&mut r)?;
if let Some(idx) = NonZeroU32::new(extra as u32) {
combining.insert(col, idx);
}
if let Some(idx) = NonZeroU32::new(link as u32) {
links.insert(col, idx);
}
cells.push(cell);
}
spans.push(Span {
line,
left,
right,
cells,
combining,
links,
});
}
let side_table_count = r.u16()?;
let mut side_table = Vec::with_capacity(side_table_count as usize);
for _ in 0..side_table_count {
let len = r.u16()?;
let mut cluster = Vec::with_capacity(len as usize);
for _ in 0..len {
cluster.push(char::from_u32(r.u32()?).ok_or(DecodeError::BadTag)?);
}
side_table.push(cluster);
}
let link_count = r.u16()?;
let mut link_table = Vec::with_capacity(link_count as usize);
for _ in 0..link_count {
let len = r.u16()? as usize;
let bytes = r.take(len)?;
link_table.push(String::from_utf8_lossy(bytes).into_owned());
}
Ok(Frame {
cols,
rows,
kind,
cursor_row,
cursor_col,
cursor_visible,
cursor_shape,
cursor_blink,
scroll,
spans,
side_table,
link_table,
})
}
fn decode_cell(r: &mut Reader) -> Result<(Cell, u16, u16), DecodeError> {
let c = char::from_u32(r.u32()?).ok_or(DecodeError::BadTag)?;
let fg = decode_color(r.u32()?)?;
let bg = decode_color(r.u32()?)?;
let flags = CellFlags::from_bits_retain(r.u16()?);
let extra = r.u16()?;
let link = r.u16()?;
let mut cell = Cell::from_parts(c, fg, bg, flags);
cell.set_combined(extra != 0);
cell.set_linked(link != 0);
Ok((cell, extra, link))
}
fn decode_color(v: u32) -> Result<Color, DecodeError> {
let payload = v & 0x00FF_FFFF;
match v >> 24 {
0 => Ok(Color::Default),
1 => Ok(Color::Indexed(payload as u8)),
2 => Ok(Color::Rgb(
(payload >> 16) as u8,
(payload >> 8) as u8,
payload as u8,
)),
_ => Err(DecodeError::BadTag),
}
}
struct Reader<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(bytes: &'a [u8]) -> Self {
Reader { bytes, pos: 0 }
}
fn take(&mut self, n: usize) -> Result<&'a [u8], DecodeError> {
let end = self.pos.checked_add(n).ok_or(DecodeError::Truncated)?;
let slice = self
.bytes
.get(self.pos..end)
.ok_or(DecodeError::Truncated)?;
self.pos = end;
Ok(slice)
}
fn u8(&mut self) -> Result<u8, DecodeError> {
Ok(self.take(1)?[0])
}
fn u16(&mut self) -> Result<u16, DecodeError> {
let b = self.take(2)?;
Ok(u16::from_le_bytes([b[0], b[1]]))
}
fn u32(&mut self) -> Result<u32, DecodeError> {
let b = self.take(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
}