use crate::error::{Result, TermTestError};
#[derive(Debug, Clone, PartialEq)]
pub struct SixelSequence {
pub raw: Vec<u8>,
pub position: (u16, u16),
pub bounds: (u16, u16, u16, u16),
}
impl SixelSequence {
pub fn new(raw: Vec<u8>, position: (u16, u16), bounds: (u16, u16, u16, u16)) -> Self {
Self {
raw,
position,
bounds,
}
}
pub fn is_within(&self, area: (u16, u16, u16, u16)) -> bool {
let (row, col, width, height) = self.bounds;
let (area_row, area_col, area_width, area_height) = area;
row >= area_row
&& col >= area_col
&& (row + height) <= (area_row + area_height)
&& (col + width) <= (area_col + area_width)
}
pub fn overlaps(&self, area: (u16, u16, u16, u16)) -> bool {
let (row, col, width, height) = self.bounds;
let (area_row, area_col, area_width, area_height) = area;
!(row + height <= area_row
|| col + width <= area_col
|| row >= area_row + area_height
|| col >= area_col + area_width)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SixelCapture {
sequences: Vec<SixelSequence>,
}
impl SixelCapture {
pub fn new() -> Self {
Self {
sequences: Vec::new(),
}
}
pub fn from_output(_output: &[u8], _cursor_positions: &[(u16, u16)]) -> Self {
Self::new()
}
pub fn from_screen_state(screen: &crate::screen::ScreenState) -> Self {
use crate::screen::SixelRegion;
let sequences = screen.sixel_regions()
.iter()
.map(|region: &SixelRegion| {
const PIXELS_PER_COL: u32 = 8;
const PIXELS_PER_ROW: u32 = 6;
let width_cells = if region.width > 0 {
((region.width + PIXELS_PER_COL - 1) / PIXELS_PER_COL) as u16
} else {
0
};
let height_cells = if region.height > 0 {
((region.height + PIXELS_PER_ROW - 1) / PIXELS_PER_ROW) as u16
} else {
0
};
SixelSequence::new(
region.data.clone(),
(region.start_row, region.start_col),
(region.start_row, region.start_col, width_cells, height_cells),
)
})
.collect();
Self { sequences }
}
pub fn sequences(&self) -> &[SixelSequence] {
&self.sequences
}
pub fn is_empty(&self) -> bool {
self.sequences.is_empty()
}
pub fn sequences_in_area(&self, area: (u16, u16, u16, u16)) -> Vec<&SixelSequence> {
self.sequences
.iter()
.filter(|seq| seq.is_within(area))
.collect()
}
pub fn sequences_outside_area(&self, area: (u16, u16, u16, u16)) -> Vec<&SixelSequence> {
self.sequences
.iter()
.filter(|seq| !seq.is_within(area))
.collect()
}
pub fn assert_all_within(&self, area: (u16, u16, u16, u16)) -> Result<()> {
let outside = self.sequences_outside_area(area);
if !outside.is_empty() {
return Err(TermTestError::SixelValidation(format!(
"Found {} Sixel sequence(s) outside area {:?}: {:?}",
outside.len(),
area,
outside.iter().map(|s| s.position).collect::<Vec<_>>()
)));
}
Ok(())
}
pub fn differs_from(&self, other: &SixelCapture) -> bool {
self.sequences != other.sequences
}
}
impl Default for SixelCapture {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sixel_sequence_within() {
let seq = SixelSequence::new(vec![], (5, 5), (5, 5, 10, 10));
assert!(seq.is_within((0, 0, 20, 20)));
assert!(!seq.is_within((0, 0, 10, 10)));
}
#[test]
fn test_sixel_sequence_overlaps() {
let seq = SixelSequence::new(vec![], (5, 5), (5, 5, 10, 10));
assert!(seq.overlaps((0, 0, 10, 10)));
assert!(seq.overlaps((10, 10, 10, 10)));
assert!(!seq.overlaps((0, 0, 5, 5)));
}
#[test]
fn test_sixel_capture_empty() {
let capture = SixelCapture::new();
assert!(capture.is_empty());
assert_eq!(capture.sequences().len(), 0);
}
#[test]
fn test_sixel_capture_filtering() {
let mut capture = SixelCapture::new();
capture.sequences.push(SixelSequence::new(vec![], (5, 5), (5, 5, 10, 10)));
capture.sequences.push(SixelSequence::new(vec![], (20, 20), (20, 20, 10, 10)));
let area = (0, 0, 15, 15);
assert_eq!(capture.sequences_in_area(area).len(), 1);
assert_eq!(capture.sequences_outside_area(area).len(), 1);
}
}