1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#![deny(missing_docs)]

//! Library to display images in the terminal.
//!
//! This library contains functionality extracted from the [`viu`](https://github.com/atanunq/viu) crate.
//! It aims to provide an easy to use interface to print images in the terminal. Uses some abstractions
//! provided by the [`image`] crate. Both the [Kitty](https://sw.kovidgoyal.net/kitty/graphics-protocol.html)
//! and [iTerm](https://iterm2.com/documentation-images.html) graphic protocols are supported.
//! By default, they are used if detected. If not, `viuer` will fallback to using regular
//! half blocks instead (▄ and ▀).
//!
//! ## Basic Usage
//! The example below shows how to print the image `img.jpg` in 40x30 terminal cells, with vertical
//! offset of 4 and horizontal of 10, starting from the top left corner. More options are available
//! through the [Config] struct.
//! ```no_run
//! use viuer::{Config, print_from_file};
//! let conf = Config {
//!     width: Some(40),
//!     height: Some(30),
//!     x: 10,
//!     y: 4,
//!     ..Default::default()
//! };
//! // will resize the image to fit in 40x30 terminal cells and print it
//! print_from_file("img.jpg", &conf).expect("Image printing failed.");
//! ```

use std::path::Path;

use crossterm::{
    cursor::{RestorePosition, SavePosition},
    execute,
};
use image::DynamicImage;
use printer::{Printer, PrinterType};

mod config;
mod error;
mod printer;
mod utils;

pub use config::Config;
pub use error::{ViuError, ViuResult};
pub use printer::{get_kitty_support, is_iterm_supported, resize, KittySupport};
pub use utils::terminal_size;

#[cfg(feature = "sixel")]
pub use printer::is_sixel_supported;

/// Default printing method. Uses either iTerm or Kitty graphics protocol, if supported,
/// and half blocks otherwise.
///
/// Check the [Config] struct for all customization options.
/// ## Example
/// The snippet below reads all of stdin, decodes it with the [`image`] crate
/// and prints it to the terminal. The image will also be resized to fit in the terminal.
///
/// ```no_run
/// use std::io::{stdin, Read};
/// use viuer::{Config, print};
///
/// let stdin = stdin();
/// let mut handle = stdin.lock();
///
/// let mut buf: Vec<u8> = Vec::new();
/// let _ = handle
///     .read_to_end(&mut buf)
///     .expect("Could not read until EOF.");
///
/// let img = image::load_from_memory(&buf).expect("Data from stdin could not be decoded.");
/// print(&img, &Config::default()).expect("Image printing failed.");
/// ```
pub fn print(img: &DynamicImage, config: &Config) -> ViuResult<(u32, u32)> {
    let mut stdout = std::io::stdout();
    if config.restore_cursor {
        execute!(&mut stdout, SavePosition)?;
    }

    let (w, h) = choose_printer(config).print(&mut stdout, img, config)?;

    if config.restore_cursor {
        execute!(&mut stdout, RestorePosition)?;
    };

    Ok((w, h))
}

/// Helper method that reads a file, tries to decode it and prints it.
///
/// ## Example
/// ```no_run
/// use viuer::{Config, print_from_file};
/// let conf = Config {
///     width: Some(30),
///     transparent: true,
///     ..Default::default()
/// };
/// // Image will be scaled down to width 30. Aspect ratio will be preserved.
/// // Also, the terminal's background color will be used instead of checkerboard pattern.
/// print_from_file("img.jpg", &conf).expect("Image printing failed.");
/// ```
pub fn print_from_file<P: AsRef<Path>>(filename: P, config: &Config) -> ViuResult<(u32, u32)> {
    let mut stdout = std::io::stdout();
    if config.restore_cursor {
        execute!(&mut stdout, SavePosition)?;
    }

    let (w, h) = choose_printer(config).print_from_file(&mut stdout, filename, config)?;

    if config.restore_cursor {
        execute!(&mut stdout, RestorePosition)?;
    };

    Ok((w, h))
}

// Choose the appropriate printer to use based on user config and availability
fn choose_printer(config: &Config) -> PrinterType {
    #[cfg(feature = "sixel")]
    if config.use_sixel && is_sixel_supported() {
        return PrinterType::Sixel;
    }

    if config.use_iterm && is_iterm_supported() {
        PrinterType::iTerm
    } else if config.use_kitty && get_kitty_support() != KittySupport::None {
        PrinterType::Kitty
    } else {
        PrinterType::Block
    }
}