uncurses/ansi/graphics.rs
1//! Inline graphics encoders for DCS and APC image protocols.
2//!
3//! ## Category
4//!
5//! This module frames image payloads as either Sixel DCS strings or APC graphics
6//! strings. It does not inspect, compress, or validate the image payload itself.
7//!
8//! ## String-control framing
9//!
10//! Both writers use 7-bit string controls terminated by `ST` (`ESC \\`):
11//!
12//! ```text
13//! ESC P ... q payload ESC \\ DCS Sixel
14//! ESC _ G opts ; payload ESC \\ APC graphics
15//! ──┬── ──┬──
16//! intro terminator
17//! ```
18//!
19//! ## Mode interaction
20//!
21//! Inline graphics are not toggled by a mode in this module. Terminals may impose
22//! their own size, capability, or security policy on received payloads.
23
24use std::io::{self, Write};
25
26/// Frame a Sixel payload as `ESC P <p1> ; <p2> [;<p3>] q <payload> ESC \`.
27///
28/// `p1` and `p2` are omitted when negative, while their semicolon separator remains. `p3` is emitted only when greater than zero. `payload` is copied verbatim between the `q` final byte and `ST`.
29pub fn write_sixel<W: Write>(
30 w: &mut W,
31 p1: i32,
32 p2: i32,
33 p3: i32,
34 payload: &[u8],
35) -> io::Result<()> {
36 w.write_all(b"\x1bP")?;
37 if p1 >= 0 {
38 write!(w, "{p1}")?;
39 }
40 w.write_all(b";")?;
41 if p2 >= 0 {
42 write!(w, "{p2}")?;
43 }
44 if p3 > 0 {
45 write!(w, ";{p3}")?;
46 }
47 w.write_all(b"q")?;
48 w.write_all(payload)?;
49 w.write_all(b"\x1b\\")
50}
51
52/// Frame a graphics payload as `ESC _ G <options> [;<payload>] ESC \`.
53///
54/// `options` are emitted verbatim and joined with commas. The semicolon before `payload` is omitted when `payload` is empty.
55pub fn write_kitty_graphics<W: Write>(
56 w: &mut W,
57 options: &[&str],
58 payload: &[u8],
59) -> io::Result<()> {
60 w.write_all(b"\x1b_G")?;
61 for (i, opt) in options.iter().enumerate() {
62 if i > 0 {
63 w.write_all(b",")?;
64 }
65 w.write_all(opt.as_bytes())?;
66 }
67 if !payload.is_empty() {
68 w.write_all(b";")?;
69 w.write_all(payload)?;
70 }
71 w.write_all(b"\x1b\\")
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_sixel_minimal() {
80 let mut buf = Vec::new();
81 write_sixel(&mut buf, 0, 1, 0, b"#0;2;0;0;0").unwrap();
82 assert_eq!(buf, b"\x1bP0;1q#0;2;0;0;0\x1b\\");
83 }
84
85 #[test]
86 fn test_kitty_graphics() {
87 let mut buf = Vec::new();
88 write_kitty_graphics(&mut buf, &["a=T", "f=32", "s=10", "v=20"], b"AAAA").unwrap();
89 assert_eq!(buf, b"\x1b_Ga=T,f=32,s=10,v=20;AAAA\x1b\\");
90 }
91}