1use crate::{result::Result, support};
2use ansi_colours::ansi256_from_rgb;
3use crossbeam_channel::{unbounded, Receiver, Sender};
4use image::{codecs::png::PngEncoder, DynamicImage, ImageEncoder};
5use std::{
6 fs::File,
7 io::{Error, Write},
8 path::PathBuf,
9};
10
11pub(crate) struct CtrlcHandler {
12 pub sender: Sender<bool>,
13 pub receiver: Receiver<bool>,
14}
15
16impl CtrlcHandler {
17 pub fn new() -> Result<Self> {
18 let (ctrlc_tx, preview_rx) = unbounded();
20 let (preview_tx, ctrlc_rx) = unbounded();
21
22 ctrlc::set_handler(move || {
23 ctrlc_tx
24 .send(true)
25 .expect("CTRL-C error: Unable to send message");
26
27 ctrlc_rx
28 .recv()
29 .expect("CTRL-C error: No response from main thread");
30 std::process::exit(0);
31 })?;
32
33 Ok(Self {
34 sender: preview_tx,
35 receiver: preview_rx,
36 })
37 }
38}
39
40#[derive(Clone, Default, Debug)]
42pub struct TermSize {
43 pub(crate) rows: u32,
45 pub(crate) cols: u32,
47 pub(crate) width: u32,
49 pub(crate) height: u32,
51}
52
53impl TermSize {
54 pub fn new(rows: u16, cols: u16, width: u16, height: u16) -> Self {
55 Self {
56 rows: u32::from(rows),
57 cols: u32::from(cols),
58 width: u32::from(width),
59 height: u32::from(height),
60 }
61 }
62
63 pub fn get_cell_size(&self) -> Option<(u32, u32)> {
65 if self.cols == 0 || self.rows == 0 {
66 return None;
67 }
68 Some((self.width / self.cols, self.height / self.rows))
69 }
70
71 pub fn from_ioctl() -> Result<Self> {
73 unsafe {
75 let mut ws = libc::winsize {
76 ws_row: 0,
77 ws_col: 0,
78 ws_xpixel: 0,
79 ws_ypixel: 0,
80 };
81 let ret = libc::ioctl(0, libc::TIOCGWINSZ, &mut ws);
82 if ret == 0 {
83 Ok(TermSize::new(
84 ws.ws_row,
85 ws.ws_col,
86 ws.ws_xpixel,
87 ws.ws_ypixel,
88 ))
89 } else {
90 Err(Error::last_os_error().into())
91 }
92 }
93 }
94}
95
96pub fn create_temp_file(prefix: &str) -> Result<(File, PathBuf)> {
98 let (tempfile, pathbuf) = tempfile::Builder::new()
99 .prefix(prefix)
100 .tempfile_in("/tmp/")?
101 .keep()?;
102
103 Ok((tempfile, pathbuf))
104}
105
106pub fn save_in_temp_file(buffer: &[u8], file: &mut File) -> Result {
108 file.write_all(buffer)?;
109 file.flush()?;
110 Ok(())
111}
112
113#[allow(dead_code)]
115pub fn save_cursor(stdout: &mut impl Write) -> Result {
116 stdout.write_all(b"\x1b[s")?;
117 stdout.flush()?;
118 Ok(())
119}
120
121#[allow(dead_code)]
123pub fn restore_cursor(stdout: &mut impl Write) -> Result {
124 stdout.write_all(b"\x1b[u")?;
125 stdout.flush()?;
126 Ok(())
127}
128
129#[allow(dead_code)]
131pub fn move_cursor_up(stdout: &mut impl Write, x: u32) -> Result {
132 let binding = format!("\x1b[{}A", x + 1);
133 stdout.write_all(binding.as_bytes())?;
134 stdout.flush()?;
135 Ok(())
136}
137
138#[allow(dead_code)]
140pub fn move_cursor_down(stdout: &mut impl Write, x: u32) -> Result {
141 let binding = format!("\x1b[{}B", x + 1);
142 stdout.write_all(binding.as_bytes())?;
143 stdout.flush()?;
144 Ok(())
145}
146
147pub fn move_cursor_column(stdout: &mut impl Write, col: u32) -> Result {
149 let binding = format!("\x1b[{}G", col + 1);
150 stdout.write_all(binding.as_bytes())?;
151 stdout.flush()?;
152 Ok(())
153}
154
155pub fn move_cursor_row(stdout: &mut impl Write, row: u32) -> Result {
157 let binding = format!("\x1b[{}d", row + 1);
158 stdout.write_all(binding.as_bytes())?;
159 stdout.flush()?;
160 Ok(())
161}
162
163pub fn move_cursor_pos(stdout: &mut impl Write, col: u32, row: u32) -> Result {
165 let binding = format!("\x1b[{};{}H", row + 1, col + 1);
166 stdout.write_all(binding.as_bytes())?;
167 stdout.flush()?;
168 Ok(())
169}
170
171pub fn move_cursor(stdout: &mut impl Write, col: Option<u32>, row: Option<u32>) -> Result {
173 match (col, row) {
174 (None, None) => Ok(()),
175 (Some(x), None) => move_cursor_column(stdout, x),
176 (None, Some(y)) => move_cursor_row(stdout, y),
177 (Some(x), Some(y)) => move_cursor_pos(stdout, x, y),
178 }
179}
180
181pub fn show_cursor(stdout: &mut impl Write) -> Result {
183 stdout.write_all(b"\x1b[?25h")?;
184 stdout.flush()?;
185 Ok(())
186}
187
188pub fn hide_cursor(stdout: &mut impl Write) -> Result {
190 stdout.write_all(b"\x1b[?25l")?;
191 stdout.flush()?;
192 Ok(())
193}
194
195pub fn handle_spacing(stdout: &mut impl Write, spacing: Option<u32>) -> Result {
197 if let Some(spacing) = spacing {
198 stdout.write_all(&b"\n".repeat(spacing as usize))?;
199 stdout.flush()?;
200 }
201 Ok(())
202}
203
204pub fn fit_in_bounds(
206 width: u32,
207 height: u32,
208 cols: Option<u32>,
209 rows: Option<u32>,
210 upscale: bool,
211) -> Result<(u32, u32)> {
212 let term_size = TermSize::from_ioctl()?;
213 let (col_size, row_size) = match term_size.get_cell_size() {
214 Some((0, 0)) | None => (15, 30),
215 Some((c, r)) => (c, r),
216 };
217 let cols = cols.unwrap_or(term_size.cols);
218 let rows = rows.unwrap_or(term_size.rows - 1);
220
221 let (bound_width, bound_height) = (cols * col_size, rows * row_size);
222
223 if !upscale && width < bound_width && height < bound_height {
224 return Ok((width / col_size, height / row_size));
225 }
226
227 let w_ratio = width * bound_height;
228 let h_ratio = bound_width * height;
229
230 if w_ratio >= h_ratio {
231 Ok((
232 cols,
233 std::cmp::max((height * bound_width) / (width * row_size), 1),
234 ))
235 } else {
236 Ok((
237 std::cmp::max((width * bound_height) / (height * col_size), 1),
238 rows,
239 ))
240 }
241}
242
243pub fn resize(image: &DynamicImage, width: u32, height: u32) -> DynamicImage {
245 image.resize_exact(width, height, image::imageops::Triangle)
246}
247
248pub fn convert_to_image_buffer(image: &DynamicImage, width: u32, height: u32) -> Result<Vec<u8>> {
251 let mut image_buffer = Vec::new();
252 PngEncoder::new(&mut image_buffer).write_image(
253 image.as_bytes(),
254 width,
255 height,
256 image.color(),
257 )?;
258 Ok(image_buffer)
259}
260
261pub fn pixel_is_transparent(rgb: [u8; 4]) -> bool {
263 rgb[3] < 25
264}
265
266pub fn ansi_rgb(rgb: [u8; 4], bg: bool) -> String {
268 if bg {
269 format!("\x1b[48;2;{};{};{}m", rgb[0], rgb[1], rgb[2])
270 } else {
271 format!("\x1b[38;2;{};{};{}m", rgb[0], rgb[1], rgb[2])
272 }
273}
274
275pub fn ansi_indexed(rgb: [u8; 4], bg: bool) -> String {
277 let index = ansi256_from_rgb((rgb[0], rgb[1], rgb[2]));
278 if bg {
279 format!("\x1b[48;5;{index}m")
280 } else {
281 format!("\x1b[38;5;{index}m")
282 }
283}
284
285pub fn ansi_color(rgb: [u8; 4], bg: bool) -> String {
287 if support::truecolor() {
288 ansi_rgb(rgb, bg)
289 } else {
290 ansi_indexed(rgb, bg)
291 }
292}