use std::borrow::Cow;
use super::sixel;
const ESC: u8 = 0x1b;
const CAN: u8 = 0x18;
const SUB: u8 = 0x1a;
pub const MAX_APC: usize = 48 << 20;
pub const MAX_SIXEL: usize = 48 << 20;
const MAX_DCS_PARAMS: usize = 64;
#[derive(Debug, PartialEq)]
pub enum Piece<'a> {
Text(Cow<'a, [u8]>),
Kitty(Vec<u8>),
Sixel { params: Vec<u8>, data: Vec<u8>, size: Option<(u32, u32)> },
}
#[derive(Default, Clone, Copy, PartialEq, Debug)]
enum State {
#[default]
Ground,
Esc,
Apc,
Kitty,
KittyEsc,
Dcs,
Sixel,
SixelEsc,
}
#[derive(Default)]
pub struct Scanner {
state: State,
buf: Vec<u8>,
params: Vec<u8>,
sixel: sixel::Measure,
overflow: bool,
}
impl Scanner {
pub fn split<'a>(&mut self, bytes: &'a [u8]) -> Vec<Piece<'a>> {
let mut out = Vec::new();
if self.state == State::Esc
&& self.buf == [ESC]
&& bytes.first().is_some_and(|b| !matches!(*b, b'_' | b'P' | ESC))
{
out.push(Piece::Text(Cow::Owned(vec![ESC])));
self.buf.clear();
self.state = State::Ground;
}
if self.state == State::Ground
&& memchr::memchr_iter(ESC, bytes).all(|at| !matches!(bytes.get(at + 1), Some(b'_' | b'P')))
{
let text = match bytes.last() {
Some(&ESC) => {
self.buf.clear();
self.buf.push(ESC);
self.state = State::Esc;
&bytes[..bytes.len() - 1]
}
_ => bytes,
};
if !text.is_empty() {
out.push(Piece::Text(Cow::Borrowed(text)));
}
return out;
}
let mut text = Vec::new();
let mut i = 0;
while i < bytes.len() {
if self.state == State::Ground {
match memchr::memchr(ESC, &bytes[i..]) {
Some(n) => {
text.extend_from_slice(&bytes[i..i + n]);
i += n;
}
None => {
text.extend_from_slice(&bytes[i..]);
break;
}
}
} else if matches!(self.state, State::Kitty | State::Sixel) {
let end = memchr::memchr3(ESC, CAN, SUB, &bytes[i..]).map_or(bytes.len(), |n| i + n);
self.body(&bytes[i..end]);
i = end;
if i == bytes.len() {
break;
}
}
let b = bytes[i];
i += 1;
match self.state {
State::Ground => {
self.buf.clear();
self.buf.push(b);
self.state = State::Esc;
}
State::Esc => self.after_esc(b, &mut text),
State::Apc if b == b'G' => {
self.buf.clear();
self.overflow = false;
self.state = State::Kitty;
}
State::Apc => {
text.extend_from_slice(&self.buf);
self.buf.clear();
self.reprocess(b, &mut text);
}
State::Kitty | State::Sixel => {
if b == ESC {
self.state = if self.state == State::Kitty { State::KittyEsc } else { State::SixelEsc };
} else {
self.abort();
}
}
State::KittyEsc | State::SixelEsc => {
if b == b'\\' {
if !self.overflow {
let body = std::mem::take(&mut self.buf);
if self.state == State::KittyEsc {
flush(&mut text, &mut out);
out.push(Piece::Kitty(body));
} else {
flush(&mut text, &mut out);
let params = std::mem::take(&mut self.params);
let size = std::mem::take(&mut self.sixel).size();
out.push(Piece::Sixel { params, data: body, size });
}
}
self.abort();
} else {
self.abort();
self.buf.push(ESC);
self.state = State::Esc;
self.after_esc(b, &mut text);
}
}
State::Dcs => match b {
0x30..=0x3f if self.params.len() < MAX_DCS_PARAMS => {
self.params.push(b);
self.buf.push(b);
}
b'q' => {
self.buf.clear();
self.sixel = sixel::Measure::default();
self.overflow = false;
self.state = State::Sixel;
}
_ => {
text.extend_from_slice(&self.buf);
self.buf.clear();
self.params.clear();
self.reprocess(b, &mut text);
}
},
}
}
flush(&mut text, &mut out);
out
}
fn body(&mut self, run: &[u8]) {
let limit = if self.state == State::Kitty { MAX_APC } else { MAX_SIXEL };
if self.overflow || self.buf.len() + run.len() > limit {
self.overflow = true;
return;
}
self.buf.extend_from_slice(run);
if self.state == State::Sixel {
self.sixel.read(run);
}
}
fn after_esc(&mut self, b: u8, text: &mut Vec<u8>) {
match b {
b'_' => {
self.buf.push(b);
self.state = State::Apc;
}
b'P' => {
self.buf.push(b);
self.params.clear();
self.state = State::Dcs;
}
ESC => {
text.extend_from_slice(&self.buf);
self.buf.clear();
self.buf.push(ESC);
}
_ => {
text.extend_from_slice(&self.buf);
text.push(b);
self.buf.clear();
self.state = State::Ground;
}
}
}
fn reprocess(&mut self, b: u8, text: &mut Vec<u8>) {
self.state = State::Ground;
if b == ESC {
self.buf.push(ESC);
self.state = State::Esc;
} else {
text.push(b);
}
}
fn abort(&mut self) {
self.state = State::Ground;
self.buf.clear();
self.params.clear();
self.overflow = false;
}
}
fn flush(text: &mut Vec<u8>, out: &mut Vec<Piece<'_>>) {
if !text.is_empty() {
out.push(Piece::Text(Cow::Owned(std::mem::take(text))));
}
}