Skip to main content

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}