use std::io::{self, Write};
use base64::Engine;
use base64::engine::general_purpose::STANDARD as B64;
use flate2::Compression;
use flate2::write::ZlibEncoder;
use crate::framebuffer::Framebuffer;
const RAW_CHUNK: usize = 4096 / 4 * 3;
const PLACEMENT: u32 = 1;
pub struct Placement {
pub src_x: u32,
pub src_y: u32,
pub src_w: u32,
pub src_h: u32,
pub z: i32,
pub cols: u32,
pub rows: u32,
}
pub fn write(out: &mut impl Write, fb: &Framebuffer, id: u32) -> io::Result<()> {
send(out, fb, id, true)
}
pub struct Encoded {
pub w: u32,
pub h: u32,
payload: Vec<u8>,
}
pub fn encode(fb: &Framebuffer) -> io::Result<Encoded> {
let mut z = ZlibEncoder::new(Vec::new(), Compression::fast());
z.write_all(fb.as_raw())?;
Ok(Encoded {
w: fb.width(),
h: fb.height(),
payload: z.finish()?,
})
}
fn send(out: &mut impl Write, fb: &Framebuffer, id: u32, display: bool) -> io::Result<()> {
emit(out, &encode(fb)?, id, display, Quiet::Fully)
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Quiet {
Fully,
ErrorsOnly,
}
impl Quiet {
fn code(self) -> u8 {
match self {
Quiet::Fully => 2,
Quiet::ErrorsOnly => 1,
}
}
}
pub fn emit(
out: &mut impl Write,
img: &Encoded,
id: u32,
display: bool,
quiet: Quiet,
) -> io::Result<()> {
let q = quiet.code();
let Encoded { w, h, payload } = img;
let action = if display { "T,C=1" } else { "t" };
let mut chunks = payload.chunks(RAW_CHUNK).peekable();
let mut first = true;
while let Some(chunk) = chunks.next() {
let more = u8::from(chunks.peek().is_some());
if first {
write!(
out,
"\x1b_Ga={action},i={id},q={q},f=32,o=z,s={w},v={h},m={more};"
)?;
first = false;
} else {
write!(out, "\x1b_Gq={q},m={more};")?;
}
out.write_all(B64.encode(chunk).as_bytes())?;
out.write_all(b"\x1b\\")?;
}
Ok(())
}
pub fn place(out: &mut impl Write, id: u32, p: &Placement) -> io::Result<()> {
write!(
out,
"\x1b_Ga=p,i={id},p={PLACEMENT},q=2,C=1,x={x},y={y},w={w},h={h},c={c},r={r},z={z};\x1b\\",
x = p.src_x,
y = p.src_y,
w = p.src_w,
h = p.src_h,
c = p.cols,
r = p.rows,
z = p.z,
)
}
const PRINT_BASE: u32 = 0xC0DE_0000;
const PRINT_SPAN: u32 = 1 << 20;
const _: () = assert!(PRINT_BASE > 0);
const _: () = assert!(PRINT_BASE.checked_add(PRINT_SPAN).is_some());
pub fn print_id(n: u32) -> u32 {
PRINT_BASE + n % PRINT_SPAN
}
pub fn forget_prints(out: &mut impl Write) -> io::Result<()> {
let last = PRINT_BASE + PRINT_SPAN - 1;
write!(out, "\x1b_Ga=d,d=R,x={PRINT_BASE},y={last},q=2;\x1b\\")
}
pub fn forget(out: &mut impl Write, id: u32) -> io::Result<()> {
write!(out, "\x1b_Ga=d,d=I,i={id},q=2;\x1b\\")
}
pub fn next_id() -> u32 {
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static BASE: OnceLock<u32> = OnceLock::new();
static N: AtomicU32 = AtomicU32::new(0);
let base = *BASE.get_or_init(|| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
(now.as_secs() as u32).rotate_left(11)
^ now.subsec_nanos()
^ std::process::id().rotate_left(19)
});
match base.wrapping_add(N.fetch_add(1, Ordering::Relaxed)) {
0 => 1,
id => id,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn payload_of(bytes: &[u8]) -> Vec<u8> {
let mut b64 = Vec::new();
let mut rest = bytes;
while let Some(start) = rest.windows(3).position(|w| w == b"\x1b_G") {
let body = &rest[start + 3..];
let semi = body.iter().position(|&c| c == b';').unwrap();
let end = body.windows(2).position(|w| w == b"\x1b\\").unwrap();
b64.extend_from_slice(&body[semi + 1..end]);
rest = &body[end + 2..];
}
let z = B64.decode(&b64).unwrap();
let mut out = Vec::new();
std::io::copy(&mut flate2::read::ZlibDecoder::new(&z[..]), &mut out).unwrap();
out
}
#[test]
fn payload_round_trips_to_the_original_pixels() {
let fb = Framebuffer::from_fn(40, 40, |x, y| {
image::Rgba([x as u8, y as u8, (x * y) as u8, 255])
});
let mut out = Vec::new();
write(&mut out, &fb, 7).unwrap();
assert_eq!(payload_of(&out), *fb.as_raw());
}
#[test]
fn a_print_id_stays_inside_the_block_it_is_deleted_with() {
for n in [0, 1, 999, PRINT_SPAN - 1, PRINT_SPAN, PRINT_SPAN + 7, u32::MAX] {
let id = print_id(n);
assert!(
(PRINT_BASE..PRINT_BASE + PRINT_SPAN).contains(&id),
"image {n} took id {id:#x}, outside the block"
);
}
}
#[test]
fn the_block_delete_names_the_whole_block() {
let mut out = Vec::new();
forget_prints(&mut out).unwrap();
let lo = print_id(0);
let hi = print_id(PRINT_SPAN - 1);
assert_eq!(
String::from_utf8(out).unwrap(),
format!("\x1b_Ga=d,d=R,x={lo},y={hi},q=2;\x1b\\")
);
}
#[test]
fn ids_are_distinct_and_never_zero() {
let ids: Vec<u32> = (0..64).map(|_| next_id()).collect();
assert!(ids.iter().all(|&i| i != 0));
let mut sorted = ids.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), ids.len(), "ids repeated within one run");
}
#[test]
fn header_declares_the_image_size_and_id() {
let fb = Framebuffer::new(3, 5);
let mut out = Vec::new();
write(&mut out, &fb, 42).unwrap();
let text = String::from_utf8_lossy(&out);
assert!(
text.starts_with("\x1b_Ga=T,C=1,i=42,q=2,f=32,o=z,s=3,v=5,m=0;"),
"{}",
&text[..44]
);
}
#[test]
fn every_chunk_but_the_last_is_flagged_and_full() {
let fb = Framebuffer::from_fn(200, 200, |x, y| {
image::Rgba([(x ^ y) as u8, (x * 7) as u8, (y * 13) as u8, 255])
});
let mut out = Vec::new();
write(&mut out, &fb, 1).unwrap();
let text = String::from_utf8_lossy(&out);
let flags: Vec<&str> = text.match_indices("m=").map(|(i, _)| &text[i + 2..i + 3]).collect();
assert!(flags.len() > 1, "test image did not chunk");
assert!(flags[..flags.len() - 1].iter().all(|f| *f == "1"));
assert_eq!(flags[flags.len() - 1], "0");
}
}