use crate::error::{Error, Result};
use crate::types::*;
use crate::{APC_END, APC_START, GRAPHICS_PREFIX, MAX_CHUNK_SIZE};
use base64::{Engine, engine::general_purpose::STANDARD};
use std::fmt;
#[derive(Debug, Clone, Default)]
pub struct CommandBuilder {
action: Option<Action>,
format: Option<ImageFormat>,
medium: Option<TransmissionMedium>,
width: Option<u32>,
height: Option<u32>,
image_id: Option<u32>,
image_number: Option<u32>,
placement_id: Option<u32>,
more_data: Option<bool>,
compression: Option<Compression>,
quiet: Option<u8>,
source_x: Option<u32>,
source_y: Option<u32>,
source_width: Option<u32>,
source_height: Option<u32>,
cell_offset_x: Option<u32>,
cell_offset_y: Option<u32>,
columns: Option<u32>,
rows: Option<u32>,
z_index: Option<i32>,
cursor_policy: Option<CursorPolicy>,
delete_target: Option<DeleteTarget>,
path: Option<String>,
data_size: Option<usize>,
data_offset: Option<usize>,
unicode_placeholder: Option<UnicodePlaceholder>,
parent_image_id: Option<u32>,
parent_placement_id: Option<u32>,
relative_h_offset: Option<i32>,
relative_v_offset: Option<i32>,
animation_control: Option<AnimationControl>,
frame_number: Option<u32>,
frame_gap: Option<i32>,
loop_count: Option<u32>,
background_color: Option<u32>,
ref_frame: Option<u32>,
composition: Option<FrameComposition>,
}
impl CommandBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn action(mut self, action: Action) -> Self {
self.action = Some(action);
self
}
pub fn format(mut self, format: ImageFormat) -> Self {
self.format = Some(format);
self
}
pub fn medium(mut self, medium: TransmissionMedium) -> Self {
self.medium = Some(medium);
self
}
pub fn dimensions(mut self, width: u32, height: u32) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
pub fn image_id(mut self, id: u32) -> Self {
self.image_id = Some(id);
self
}
pub fn image_number(mut self, number: u32) -> Self {
self.image_number = Some(number);
self
}
pub fn placement_id(mut self, id: u32) -> Self {
self.placement_id = Some(id);
self
}
pub fn more_data(mut self, more: bool) -> Self {
self.more_data = Some(more);
self
}
pub fn compression(mut self, compression: Compression) -> Self {
self.compression = Some(compression);
self
}
pub fn quiet(mut self, mode: u8) -> Self {
self.quiet = Some(mode);
self
}
pub fn source_rect(mut self, x: u32, y: u32, width: u32, height: u32) -> Self {
self.source_x = Some(x);
self.source_y = Some(y);
self.source_width = Some(width);
self.source_height = Some(height);
self
}
pub fn cell_offset(mut self, x: u32, y: u32) -> Self {
self.cell_offset_x = Some(x);
self.cell_offset_y = Some(y);
self
}
pub fn display_area(mut self, columns: u32, rows: u32) -> Self {
self.columns = Some(columns);
self.rows = Some(rows);
self
}
pub fn z_index(mut self, z: i32) -> Self {
self.z_index = Some(z);
self
}
pub fn cursor_policy(mut self, policy: CursorPolicy) -> Self {
self.cursor_policy = Some(policy);
self
}
pub fn delete_target(mut self, target: DeleteTarget) -> Self {
self.delete_target = Some(target);
self
}
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into());
self
}
pub fn data_range(mut self, size: usize, offset: usize) -> Self {
self.data_size = Some(size);
self.data_offset = Some(offset);
self
}
pub fn unicode_placeholder(mut self, columns: u16, rows: u16) -> Self {
self.unicode_placeholder = Some(UnicodePlaceholder { columns, rows });
self
}
pub fn parent(mut self, image_id: u32, placement_id: u32) -> Self {
self.parent_image_id = Some(image_id);
self.parent_placement_id = Some(placement_id);
self
}
pub fn relative_offset(mut self, h: i32, v: i32) -> Self {
self.relative_h_offset = Some(h);
self.relative_v_offset = Some(v);
self
}
pub fn animation_control(mut self, control: AnimationControl) -> Self {
self.animation_control = Some(control);
self
}
pub fn frame_number(mut self, frame: u32) -> Self {
self.frame_number = Some(frame);
self
}
pub fn frame_gap(mut self, gap_ms: i32) -> Self {
self.frame_gap = Some(gap_ms);
self
}
pub fn loop_count(mut self, count: u32) -> Self {
self.loop_count = Some(count);
self
}
pub fn background_color(mut self, color: u32) -> Self {
self.background_color = Some(color);
self
}
pub fn ref_frame(mut self, frame: u32) -> Self {
self.ref_frame = Some(frame);
self
}
pub fn composition(mut self, comp: FrameComposition) -> Self {
self.composition = Some(comp);
self
}
pub fn build(self) -> Command {
Command { inner: self }
}
}
#[derive(Debug, Clone)]
pub struct Command {
inner: CommandBuilder,
}
impl Command {
pub fn builder() -> CommandBuilder {
CommandBuilder::new()
}
fn build_control_data(&self) -> String {
let mut parts = Vec::new();
if let Some(action) = &self.inner.action {
parts.push(format!("a={action}"));
}
if let Some(format) = &self.inner.format {
parts.push(format!("f={format}"));
}
if let Some(medium) = &self.inner.medium {
parts.push(format!("t={medium}"));
}
if let Some(width) = self.inner.width {
parts.push(format!("s={width}"));
}
if let Some(height) = self.inner.height {
parts.push(format!("v={height}"));
}
if let Some(id) = self.inner.image_id {
parts.push(format!("i={id}"));
} else if let Some(num) = self.inner.image_number {
parts.push(format!("I={num}"));
}
if let Some(id) = self.inner.placement_id {
parts.push(format!("p={id}"));
}
if let Some(more) = self.inner.more_data {
parts.push(format!("m={}", if more { 1 } else { 0 }));
}
if let Some(comp) = &self.inner.compression {
parts.push(format!("o={comp}"));
}
if let Some(quiet) = self.inner.quiet {
parts.push(format!("q={quiet}"));
}
if let Some(x) = self.inner.source_x {
parts.push(format!("x={x}"));
}
if let Some(y) = self.inner.source_y {
parts.push(format!("y={y}"));
}
if let Some(w) = self.inner.source_width {
parts.push(format!("w={w}"));
}
if let Some(h) = self.inner.source_height {
parts.push(format!("h={h}"));
}
if let Some(x) = self.inner.cell_offset_x {
parts.push(format!("X={x}"));
}
if let Some(y) = self.inner.cell_offset_y {
parts.push(format!("Y={y}"));
}
if let Some(cols) = self.inner.columns {
parts.push(format!("c={cols}"));
}
if let Some(rows) = self.inner.rows {
parts.push(format!("r={rows}"));
}
if let Some(z) = self.inner.z_index {
parts.push(format!("z={z}"));
}
if let Some(policy) = &self.inner.cursor_policy
&& matches!(policy, CursorPolicy::NoMove)
{
parts.push(format!("C={policy}"));
}
if let Some(target) = &self.inner.delete_target {
parts.push(format!("d={}", target.code()));
}
if let Some(_path) = &self.inner.path {
}
if let Some(size) = self.inner.data_size {
parts.push(format!("S={size}"));
}
if let Some(offset) = self.inner.data_offset {
parts.push(format!("O={offset}"));
}
if self.inner.unicode_placeholder.is_some() {
parts.push("U=1".to_string());
}
if let Some(id) = self.inner.parent_image_id {
parts.push(format!("P={id}"));
}
if let Some(id) = self.inner.parent_placement_id {
parts.push(format!("Q={id}"));
}
if let Some(h) = self.inner.relative_h_offset {
parts.push(format!("H={h}"));
}
if let Some(v) = self.inner.relative_v_offset {
parts.push(format!("V={v}"));
}
if let Some(control) = &self.inner.animation_control {
parts.push(format!("s={control}"));
}
if let Some(frame) = self.inner.frame_number {
parts.push(format!("c={frame}"));
}
if let Some(gap) = self.inner.frame_gap
&& gap != 0
{
parts.push(format!("z={gap}"));
}
if let Some(count) = self.inner.loop_count
&& count > 0
{
parts.push(format!("v={count}"));
}
if let Some(color) = self.inner.background_color {
parts.push(format!("Y={color}"));
}
parts.join(",")
}
pub fn serialize(&self, data: &[u8]) -> Result<String> {
let control = self.build_control_data();
let encoded = STANDARD.encode(data);
let mut result = Vec::new();
result.extend_from_slice(APC_START);
result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
result.extend_from_slice(control.as_bytes());
result.push(b';');
result.extend_from_slice(encoded.as_bytes());
result.extend_from_slice(APC_END);
String::from_utf8(result).map_err(Error::from)
}
pub fn serialize_bytes(&self, data: &[u8]) -> Result<Vec<u8>> {
let control = self.build_control_data();
let encoded = STANDARD.encode(data);
let mut result = Vec::new();
result.extend_from_slice(APC_START);
result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
result.extend_from_slice(control.as_bytes());
result.push(b';');
result.extend_from_slice(encoded.as_bytes());
result.extend_from_slice(APC_END);
Ok(result)
}
pub fn serialize_chunked(&self, data: &[u8]) -> Result<ChunkedSerializer> {
let encoded = STANDARD.encode(data);
let chunk_size = (MAX_CHUNK_SIZE / 4) * 4;
Ok(ChunkedSerializer {
control: self.build_control_data(),
encoded,
chunk_size,
offset: 0,
is_first: true,
})
}
pub fn serialize_with_path(&self) -> Result<String> {
let control = self.build_control_data();
let path = self
.inner
.path
.as_ref()
.ok_or(Error::MissingField("path"))?;
let encoded_path = STANDARD.encode(path.as_bytes());
let mut result = Vec::new();
result.extend_from_slice(APC_START);
result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
result.extend_from_slice(control.as_bytes());
result.push(b';');
result.extend_from_slice(encoded_path.as_bytes());
result.extend_from_slice(APC_END);
String::from_utf8(result).map_err(Error::from)
}
}
pub struct ChunkedSerializer {
control: String,
encoded: String,
chunk_size: usize,
offset: usize,
is_first: bool,
}
impl ChunkedSerializer {
pub fn total_chunks(&self) -> usize {
self.encoded.len().div_ceil(self.chunk_size)
}
pub fn has_more(&self) -> bool {
self.offset < self.encoded.len()
}
}
impl Iterator for ChunkedSerializer {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.encoded.len() {
return None;
}
let end = (self.offset + self.chunk_size).min(self.encoded.len());
let chunk = &self.encoded[self.offset..end];
let is_last = end >= self.encoded.len();
let mut result = Vec::new();
result.extend_from_slice(APC_START);
result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
if self.is_first {
result.extend_from_slice(self.control.as_bytes());
result.push(b',');
self.is_first = false;
}
result.extend_from_slice(format!("m={}", if is_last { 0 } else { 1 }).as_bytes());
result.push(b';');
result.extend_from_slice(chunk.as_bytes());
result.extend_from_slice(APC_END);
self.offset = end;
String::from_utf8(result).ok()
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Command({})", self.build_control_data())
}
}
impl Command {
pub fn query_support() -> Self {
Self::builder().action(Action::Query).quiet(2).build()
}
pub fn transmit_png(data: &[u8]) -> Result<Vec<String>> {
let cmd = Self::builder()
.action(Action::TransmitAndDisplay)
.format(ImageFormat::Png)
.quiet(2)
.build();
let chunks: Vec<String> = cmd.serialize_chunked(data)?.collect();
Ok(chunks)
}
pub fn transmit_rgba(data: &[u8], width: u32, height: u32) -> Result<Vec<String>> {
let expected_size = (width * height * 4) as usize;
if data.len() != expected_size {
return Err(Error::InvalidDimensions { width, height });
}
let cmd = Self::builder()
.action(Action::TransmitAndDisplay)
.format(ImageFormat::Rgba)
.dimensions(width, height)
.quiet(2)
.build();
let chunks: Vec<String> = cmd.serialize_chunked(data)?.collect();
Ok(chunks)
}
pub fn transmit_rgb(data: &[u8], width: u32, height: u32) -> Result<Vec<String>> {
let expected_size = (width * height * 3) as usize;
if data.len() != expected_size {
return Err(Error::InvalidDimensions { width, height });
}
let cmd = Self::builder()
.action(Action::TransmitAndDisplay)
.format(ImageFormat::Rgb)
.dimensions(width, height)
.quiet(2)
.build();
let chunks: Vec<String> = cmd.serialize_chunked(data)?.collect();
Ok(chunks)
}
pub fn delete_all() -> Self {
Self::builder()
.action(Action::Delete)
.delete_target(DeleteTarget::All)
.build()
}
pub fn delete_by_id(image_id: u32) -> Self {
Self::builder()
.action(Action::Delete)
.delete_target(DeleteTarget::ById { free_data: true })
.image_id(image_id)
.build()
}
pub fn place(image_id: u32, columns: u32, rows: u32) -> Self {
Self::builder()
.action(Action::Place)
.image_id(image_id)
.display_area(columns, rows)
.build()
}
}