1use crate::error::{Error, Result};
4use crate::types::*;
5use crate::{APC_END, APC_START, GRAPHICS_PREFIX, MAX_CHUNK_SIZE};
6use base64::{Engine, engine::general_purpose::STANDARD};
7use std::fmt;
8
9#[derive(Debug, Clone, Default)]
11pub struct CommandBuilder {
12 action: Option<Action>,
14 format: Option<ImageFormat>,
16 medium: Option<TransmissionMedium>,
18 width: Option<u32>,
20 height: Option<u32>,
22 image_id: Option<u32>,
24 image_number: Option<u32>,
26 placement_id: Option<u32>,
28 more_data: Option<bool>,
30 compression: Option<Compression>,
32 quiet: Option<u8>,
34 source_x: Option<u32>,
36 source_y: Option<u32>,
38 source_width: Option<u32>,
40 source_height: Option<u32>,
42 cell_offset_x: Option<u32>,
44 cell_offset_y: Option<u32>,
46 columns: Option<u32>,
48 rows: Option<u32>,
50 z_index: Option<i32>,
52 cursor_policy: Option<CursorPolicy>,
54 delete_target: Option<DeleteTarget>,
56 path: Option<String>,
58 data_size: Option<usize>,
60 data_offset: Option<usize>,
62 unicode_placeholder: Option<UnicodePlaceholder>,
64 parent_image_id: Option<u32>,
66 parent_placement_id: Option<u32>,
68 relative_h_offset: Option<i32>,
70 relative_v_offset: Option<i32>,
72 animation_control: Option<AnimationControl>,
74 frame_number: Option<u32>,
76 frame_gap: Option<i32>,
78 loop_count: Option<u32>,
80 background_color: Option<u32>,
82 ref_frame: Option<u32>,
84 composition: Option<FrameComposition>,
86}
87
88impl CommandBuilder {
89 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn action(mut self, action: Action) -> Self {
96 self.action = Some(action);
97 self
98 }
99
100 pub fn format(mut self, format: ImageFormat) -> Self {
102 self.format = Some(format);
103 self
104 }
105
106 pub fn medium(mut self, medium: TransmissionMedium) -> Self {
108 self.medium = Some(medium);
109 self
110 }
111
112 pub fn dimensions(mut self, width: u32, height: u32) -> Self {
114 self.width = Some(width);
115 self.height = Some(height);
116 self
117 }
118
119 pub fn image_id(mut self, id: u32) -> Self {
121 self.image_id = Some(id);
122 self
123 }
124
125 pub fn image_number(mut self, number: u32) -> Self {
127 self.image_number = Some(number);
128 self
129 }
130
131 pub fn placement_id(mut self, id: u32) -> Self {
133 self.placement_id = Some(id);
134 self
135 }
136
137 pub fn more_data(mut self, more: bool) -> Self {
139 self.more_data = Some(more);
140 self
141 }
142
143 pub fn compression(mut self, compression: Compression) -> Self {
145 self.compression = Some(compression);
146 self
147 }
148
149 pub fn quiet(mut self, mode: u8) -> Self {
151 self.quiet = Some(mode);
152 self
153 }
154
155 pub fn source_rect(mut self, x: u32, y: u32, width: u32, height: u32) -> Self {
157 self.source_x = Some(x);
158 self.source_y = Some(y);
159 self.source_width = Some(width);
160 self.source_height = Some(height);
161 self
162 }
163
164 pub fn cell_offset(mut self, x: u32, y: u32) -> Self {
166 self.cell_offset_x = Some(x);
167 self.cell_offset_y = Some(y);
168 self
169 }
170
171 pub fn display_area(mut self, columns: u32, rows: u32) -> Self {
173 self.columns = Some(columns);
174 self.rows = Some(rows);
175 self
176 }
177
178 pub fn z_index(mut self, z: i32) -> Self {
180 self.z_index = Some(z);
181 self
182 }
183
184 pub fn cursor_policy(mut self, policy: CursorPolicy) -> Self {
186 self.cursor_policy = Some(policy);
187 self
188 }
189
190 pub fn delete_target(mut self, target: DeleteTarget) -> Self {
192 self.delete_target = Some(target);
193 self
194 }
195
196 pub fn path(mut self, path: impl Into<String>) -> Self {
198 self.path = Some(path.into());
199 self
200 }
201
202 pub fn data_range(mut self, size: usize, offset: usize) -> Self {
204 self.data_size = Some(size);
205 self.data_offset = Some(offset);
206 self
207 }
208
209 pub fn unicode_placeholder(mut self, columns: u16, rows: u16) -> Self {
211 self.unicode_placeholder = Some(UnicodePlaceholder { columns, rows });
212 self
213 }
214
215 pub fn parent(mut self, image_id: u32, placement_id: u32) -> Self {
217 self.parent_image_id = Some(image_id);
218 self.parent_placement_id = Some(placement_id);
219 self
220 }
221
222 pub fn relative_offset(mut self, h: i32, v: i32) -> Self {
224 self.relative_h_offset = Some(h);
225 self.relative_v_offset = Some(v);
226 self
227 }
228
229 pub fn animation_control(mut self, control: AnimationControl) -> Self {
231 self.animation_control = Some(control);
232 self
233 }
234
235 pub fn frame_number(mut self, frame: u32) -> Self {
237 self.frame_number = Some(frame);
238 self
239 }
240
241 pub fn frame_gap(mut self, gap_ms: i32) -> Self {
243 self.frame_gap = Some(gap_ms);
244 self
245 }
246
247 pub fn loop_count(mut self, count: u32) -> Self {
249 self.loop_count = Some(count);
250 self
251 }
252
253 pub fn background_color(mut self, color: u32) -> Self {
255 self.background_color = Some(color);
256 self
257 }
258
259 pub fn ref_frame(mut self, frame: u32) -> Self {
261 self.ref_frame = Some(frame);
262 self
263 }
264
265 pub fn composition(mut self, comp: FrameComposition) -> Self {
267 self.composition = Some(comp);
268 self
269 }
270
271 pub fn build(self) -> Command {
273 Command { inner: self }
274 }
275}
276
277#[derive(Debug, Clone)]
279pub struct Command {
280 inner: CommandBuilder,
281}
282
283impl Command {
284 pub fn builder() -> CommandBuilder {
286 CommandBuilder::new()
287 }
288
289 fn build_control_data(&self) -> String {
291 let mut parts = Vec::new();
292
293 if let Some(action) = &self.inner.action {
295 parts.push(format!("a={action}"));
296 }
297
298 if let Some(format) = &self.inner.format {
300 parts.push(format!("f={format}"));
301 }
302
303 if let Some(medium) = &self.inner.medium {
305 parts.push(format!("t={medium}"));
306 }
307
308 if let Some(width) = self.inner.width {
310 parts.push(format!("s={width}"));
311 }
312 if let Some(height) = self.inner.height {
313 parts.push(format!("v={height}"));
314 }
315
316 if let Some(id) = self.inner.image_id {
318 parts.push(format!("i={id}"));
319 } else if let Some(num) = self.inner.image_number {
320 parts.push(format!("I={num}"));
321 }
322
323 if let Some(id) = self.inner.placement_id {
325 parts.push(format!("p={id}"));
326 }
327
328 if let Some(more) = self.inner.more_data {
330 parts.push(format!("m={}", if more { 1 } else { 0 }));
331 }
332
333 if let Some(comp) = &self.inner.compression {
335 parts.push(format!("o={comp}"));
336 }
337
338 if let Some(quiet) = self.inner.quiet {
340 parts.push(format!("q={quiet}"));
341 }
342
343 if let Some(x) = self.inner.source_x {
345 parts.push(format!("x={x}"));
346 }
347 if let Some(y) = self.inner.source_y {
348 parts.push(format!("y={y}"));
349 }
350 if let Some(w) = self.inner.source_width {
351 parts.push(format!("w={w}"));
352 }
353 if let Some(h) = self.inner.source_height {
354 parts.push(format!("h={h}"));
355 }
356
357 if let Some(x) = self.inner.cell_offset_x {
359 parts.push(format!("X={x}"));
360 }
361 if let Some(y) = self.inner.cell_offset_y {
362 parts.push(format!("Y={y}"));
363 }
364
365 if let Some(cols) = self.inner.columns {
367 parts.push(format!("c={cols}"));
368 }
369 if let Some(rows) = self.inner.rows {
370 parts.push(format!("r={rows}"));
371 }
372
373 if let Some(z) = self.inner.z_index {
375 parts.push(format!("z={z}"));
376 }
377
378 if let Some(policy) = &self.inner.cursor_policy
380 && matches!(policy, CursorPolicy::NoMove)
381 {
382 parts.push(format!("C={policy}"));
383 }
384
385 if let Some(target) = &self.inner.delete_target {
387 parts.push(format!("d={}", target.code()));
388 }
389
390 if let Some(_path) = &self.inner.path {
392 }
395
396 if let Some(size) = self.inner.data_size {
398 parts.push(format!("S={size}"));
399 }
400 if let Some(offset) = self.inner.data_offset {
401 parts.push(format!("O={offset}"));
402 }
403
404 if self.inner.unicode_placeholder.is_some() {
406 parts.push("U=1".to_string());
407 }
408
409 if let Some(id) = self.inner.parent_image_id {
411 parts.push(format!("P={id}"));
412 }
413 if let Some(id) = self.inner.parent_placement_id {
414 parts.push(format!("Q={id}"));
415 }
416
417 if let Some(h) = self.inner.relative_h_offset {
419 parts.push(format!("H={h}"));
420 }
421 if let Some(v) = self.inner.relative_v_offset {
422 parts.push(format!("V={v}"));
423 }
424
425 if let Some(control) = &self.inner.animation_control {
429 parts.push(format!("s={control}"));
430 }
431
432 if let Some(frame) = self.inner.frame_number {
436 parts.push(format!("c={frame}"));
439 }
440
441 if let Some(gap) = self.inner.frame_gap
444 && gap != 0
445 {
446 parts.push(format!("z={gap}"));
447 }
448
449 if let Some(count) = self.inner.loop_count
452 && count > 0
453 {
454 parts.push(format!("v={count}"));
455 }
456
457 if let Some(color) = self.inner.background_color {
459 parts.push(format!("Y={color}"));
460 }
461
462 parts.join(",")
466 }
467
468 pub fn serialize(&self, data: &[u8]) -> Result<String> {
470 let control = self.build_control_data();
471 let encoded = STANDARD.encode(data);
472
473 let mut result = Vec::new();
474
475 result.extend_from_slice(APC_START);
477 result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
478
479 result.extend_from_slice(control.as_bytes());
481
482 result.push(b';');
484 result.extend_from_slice(encoded.as_bytes());
485
486 result.extend_from_slice(APC_END);
488
489 String::from_utf8(result).map_err(Error::from)
490 }
491
492 pub fn serialize_bytes(&self, data: &[u8]) -> Result<Vec<u8>> {
494 let control = self.build_control_data();
495 let encoded = STANDARD.encode(data);
496
497 let mut result = Vec::new();
498
499 result.extend_from_slice(APC_START);
500 result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
501 result.extend_from_slice(control.as_bytes());
502 result.push(b';');
503 result.extend_from_slice(encoded.as_bytes());
504 result.extend_from_slice(APC_END);
505
506 Ok(result)
507 }
508
509 pub fn serialize_chunked(&self, data: &[u8]) -> Result<ChunkedSerializer> {
512 let encoded = STANDARD.encode(data);
514
515 let chunk_size = (MAX_CHUNK_SIZE / 4) * 4;
517
518 Ok(ChunkedSerializer {
519 control: self.build_control_data(),
520 encoded,
521 chunk_size,
522 offset: 0,
523 is_first: true,
524 })
525 }
526
527 pub fn serialize_with_path(&self) -> Result<String> {
529 let control = self.build_control_data();
530 let path = self
531 .inner
532 .path
533 .as_ref()
534 .ok_or(Error::MissingField("path"))?;
535 let encoded_path = STANDARD.encode(path.as_bytes());
536
537 let mut result = Vec::new();
538
539 result.extend_from_slice(APC_START);
540 result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
541 result.extend_from_slice(control.as_bytes());
542 result.push(b';');
543 result.extend_from_slice(encoded_path.as_bytes());
544 result.extend_from_slice(APC_END);
545
546 String::from_utf8(result).map_err(Error::from)
547 }
548}
549
550pub struct ChunkedSerializer {
552 control: String,
553 encoded: String,
554 chunk_size: usize,
555 offset: usize,
556 is_first: bool,
557}
558
559impl ChunkedSerializer {
560 pub fn total_chunks(&self) -> usize {
562 self.encoded.len().div_ceil(self.chunk_size)
563 }
564
565 pub fn has_more(&self) -> bool {
567 self.offset < self.encoded.len()
568 }
569}
570
571impl Iterator for ChunkedSerializer {
572 type Item = String;
573
574 fn next(&mut self) -> Option<Self::Item> {
575 if self.offset >= self.encoded.len() {
576 return None;
577 }
578
579 let end = (self.offset + self.chunk_size).min(self.encoded.len());
580 let chunk = &self.encoded[self.offset..end];
581 let is_last = end >= self.encoded.len();
582
583 let mut result = Vec::new();
584 result.extend_from_slice(APC_START);
585 result.extend_from_slice(GRAPHICS_PREFIX.as_bytes());
586
587 if self.is_first {
588 result.extend_from_slice(self.control.as_bytes());
590 result.push(b',');
591 self.is_first = false;
592 }
593
594 result.extend_from_slice(format!("m={}", if is_last { 0 } else { 1 }).as_bytes());
596 result.push(b';');
597 result.extend_from_slice(chunk.as_bytes());
598 result.extend_from_slice(APC_END);
599
600 self.offset = end;
601
602 String::from_utf8(result).ok()
603 }
604}
605
606impl fmt::Display for Command {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 write!(f, "Command({})", self.build_control_data())
609 }
610}
611
612impl Command {
614 pub fn query_support() -> Self {
616 Self::builder().action(Action::Query).quiet(2).build()
617 }
618
619 pub fn transmit_png(data: &[u8]) -> Result<Vec<String>> {
621 let cmd = Self::builder()
622 .action(Action::TransmitAndDisplay)
623 .format(ImageFormat::Png)
624 .quiet(2)
625 .build();
626
627 let chunks: Vec<String> = cmd.serialize_chunked(data)?.collect();
628 Ok(chunks)
629 }
630
631 pub fn transmit_rgba(data: &[u8], width: u32, height: u32) -> Result<Vec<String>> {
633 let expected_size = (width * height * 4) as usize;
634 if data.len() != expected_size {
635 return Err(Error::InvalidDimensions { width, height });
636 }
637
638 let cmd = Self::builder()
639 .action(Action::TransmitAndDisplay)
640 .format(ImageFormat::Rgba)
641 .dimensions(width, height)
642 .quiet(2)
643 .build();
644
645 let chunks: Vec<String> = cmd.serialize_chunked(data)?.collect();
646 Ok(chunks)
647 }
648
649 pub fn transmit_rgb(data: &[u8], width: u32, height: u32) -> Result<Vec<String>> {
651 let expected_size = (width * height * 3) as usize;
652 if data.len() != expected_size {
653 return Err(Error::InvalidDimensions { width, height });
654 }
655
656 let cmd = Self::builder()
657 .action(Action::TransmitAndDisplay)
658 .format(ImageFormat::Rgb)
659 .dimensions(width, height)
660 .quiet(2)
661 .build();
662
663 let chunks: Vec<String> = cmd.serialize_chunked(data)?.collect();
664 Ok(chunks)
665 }
666
667 pub fn delete_all() -> Self {
669 Self::builder()
670 .action(Action::Delete)
671 .delete_target(DeleteTarget::All)
672 .build()
673 }
674
675 pub fn delete_by_id(image_id: u32) -> Self {
677 Self::builder()
678 .action(Action::Delete)
679 .delete_target(DeleteTarget::ById { free_data: true })
680 .image_id(image_id)
681 .build()
682 }
683
684 pub fn place(image_id: u32, columns: u32, rows: u32) -> Self {
686 Self::builder()
687 .action(Action::Place)
688 .image_id(image_id)
689 .display_area(columns, rows)
690 .build()
691 }
692}